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
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
33 * @implements {WebInspector.ViewportElement}
34 * @param {!WebInspector.ConsoleMessage} consoleMessage
35 * @param {!WebInspector.Linkifier} linkifier
36 * @param {number} nestingLevel
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>} */
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
64 this._previewFormatter = new WebInspector.RemoteObjectPreviewFormatter();
65 this._searchRegex = null;
68 WebInspector.ConsoleViewMessage.prototype = {
70 * @return {?WebInspector.Target}
74 return this.consoleMessage().target();
83 return this.toMessageElement();
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();
102 cacheFastHeight: function()
104 this._cachedHeight = this.contentElement().offsetHeight;
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);
122 fastHeight: function()
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;
132 return defaultConsoleRowHeight;
136 * @return {!WebInspector.ConsoleMessage}
138 consoleMessage: function()
140 return this._message;
143 _formatMessage: function()
145 this._formattedMessage = createElement("span");
146 this._formattedMessage.appendChild(WebInspector.Widget.createStyleElement("components/objectValue.css"));
147 this._formattedMessage.className = "console-message-text source-code";
150 * @param {string} title
152 * @this {WebInspector.ConsoleMessage}
154 function linkifyRequest(title)
156 return WebInspector.Linkifier.linkifyUsingRevealer(/** @type {!WebInspector.NetworkRequest} */ (this.request), title, this.request.url);
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()"]);
166 case WebInspector.ConsoleMessage.MessageType.Clear:
167 this._messageElement = createTextNode(WebInspector.UIString("Console was cleared"));
168 this._formattedMessage.classList.add("console-info");
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);
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);
181 case WebInspector.ConsoleMessage.MessageType.Profile:
182 case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
183 this._messageElement = this._format([consoleMessage.messageText]);
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);
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);
201 this._messageElement.createTextChildren(" ", String(consoleMessage.request.statusCode), " (", consoleMessage.request.statusText, ")");
203 var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(consoleMessage.messageText, linkifyRequest.bind(consoleMessage));
204 this._messageElement.appendChild(fragment);
207 var url = consoleMessage.url;
209 var isExternal = !WebInspector.resourceForURL(url) && !WebInspector.networkMapping.uiSourceCodeForURLForAnyTarget(url);
210 this._anchorElement = WebInspector.linkifyURLAsNode(url, url, "console-message-url", isExternal);
212 this._messageElement = this._format([consoleMessage.messageText]);
215 var args = consoleMessage.parameters || [consoleMessage.messageText];
216 this._messageElement = this._format(args);
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);
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);
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);
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)
254 this._populateStackTraceTreeElement(root);
255 this._formattedMessage = treeOutline.element;
262 formattedMessage: function()
264 if (!this._formattedMessage)
265 this._formatMessage();
266 return this._formattedMessage;
270 * @param {string} url
271 * @param {number} lineNumber
272 * @param {number} columnNumber
275 _linkifyLocation: function(url, lineNumber, columnNumber)
277 var target = this._target();
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");
287 * @param {!ConsoleAgent.CallFrame} callFrame
290 _linkifyCallFrame: function(callFrame)
292 var target = this._target();
293 return this._linkifier.linkifyConsoleCallFrame(target, callFrame, "console-message-url");
297 * @param {string} scriptId
298 * @param {string} url
299 * @param {number} lineNumber
300 * @param {number} columnNumber
303 _linkifyScriptId: function(scriptId, url, lineNumber, columnNumber)
305 var target = this._target();
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");
314 _format: function(parameters)
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)
331 parameters[i] = WebInspector.RemoteObject.fromLocalObject(parameters[i]);
335 if (typeof parameters[i] === "object")
336 parameters[i] = target.runtimeModel.createRemoteObject(parameters[i]);
338 parameters[i] = target.runtimeModel.createRemoteObjectFromPrimitiveValue(parameters[i]);
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(" ");
353 if (this._message.type === WebInspector.ConsoleMessage.MessageType.Table) {
354 formattedResult.appendChild(this._formatParameterAsTable(parameters));
355 return formattedResult;
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));
364 formattedResult.appendChild(this._formatParameter(parameters[i], false, true));
365 if (i < parameters.length - 1)
366 formattedResult.createTextChild(" ");
368 return formattedResult;
372 * @param {!WebInspector.RemoteObject} output
373 * @param {boolean=} forceObjectFormat
374 * @param {boolean=} includePreview
377 _formatParameter: function(output, forceObjectFormat, includePreview)
379 if (output.customPreview()) {
380 return (new WebInspector.CustomPreviewComponent(output)).element;
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);
392 * @param {!WebInspector.RemoteObject} obj
393 * @param {!Element} elem
395 _formatParameterAsValue: function(obj, elem)
397 elem.createTextChild(obj.description || "");
399 elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
403 * @param {!WebInspector.RemoteObject} obj
404 * @param {!Element} elem
405 * @param {boolean=} includePreview
407 _formatParameterAsObject: function(obj, elem, includePreview)
409 this._formatParameterAsArrayOrObject(obj, elem, includePreview);
413 * @param {!WebInspector.RemoteObject} obj
414 * @param {!Element} elem
415 * @param {boolean=} includePreview
417 _formatParameterAsArrayOrObject: function(obj, elem, includePreview)
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);
424 elem.appendChild(titleElement);
425 titleElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, obj), false);
429 if (obj.type === "function") {
430 WebInspector.ObjectPropertiesSection.formatObjectAsFunction(obj, titleElement, false);
431 titleElement.classList.add("object-value-function");
433 titleElement.createTextChild(obj.description || "");
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");
445 * @param {!WebInspector.RemoteObject} func
446 * @param {!Element} element
447 * @param {boolean=} includePreview
449 _formatParameterAsFunction: function(func, element, includePreview)
451 WebInspector.ObjectPropertiesSection.formatObjectAsFunction(func, element, true, includePreview);
452 element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, func), false);
456 * @param {!WebInspector.RemoteObject} obj
457 * @param {!Event} event
459 _contextMenuEventFired: function(obj, event)
461 var contextMenu = new WebInspector.ContextMenu(event);
462 contextMenu.appendApplicableItems(obj);
467 * @param {?WebInspector.RemoteObject} object
468 * @param {!Array.<!RuntimeAgent.PropertyPreview>} propertyPath
471 _renderPropertyPreviewOrAccessor: function(object, propertyPath)
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);
480 * @param {!WebInspector.RemoteObject} object
481 * @param {!Element} elem
483 _formatParameterAsNode: function(object, elem)
485 WebInspector.Renderer.renderPromise(object).then(appendRenderer, failedToRender.bind(this));
487 * @param {!Element} rendererElement
489 function appendRenderer(rendererElement)
491 elem.appendChild(rendererElement);
495 * @this {WebInspector.ConsoleViewMessage}
497 function failedToRender()
499 this._formatParameterAsObject(object, elem, false);
504 * @param {!WebInspector.RemoteObject} array
507 useArrayPreviewInFormatter: function(array)
509 return this._message.type !== WebInspector.ConsoleMessage.MessageType.DirXML;
513 * @param {!WebInspector.RemoteObject} array
514 * @param {!Element} elem
516 _formatParameterAsArray: function(array, elem)
518 var maxFlatArrayLength = 100;
519 if (this.useArrayPreviewInFormatter(array) || array.arrayLength() > maxFlatArrayLength)
520 this._formatParameterAsArrayOrObject(array, elem, this.useArrayPreviewInFormatter(array) || array.arrayLength() <= maxFlatArrayLength);
522 array.getAllProperties(false, this._printArray.bind(this, array, elem));
526 * @param {!Array.<!WebInspector.RemoteObject>} parameters
529 _formatParameterAsTable: function(parameters)
531 var element = createElementWithClass("div", "console-message-formatted-table");
532 var table = parameters[0];
533 if (!table || !table.preview)
536 var columnNames = [];
537 var preview = table.preview;
539 for (var i = 0; i < preview.properties.length; ++i) {
540 var rowProperty = preview.properties[i];
541 var rowPreview = rowProperty.valuePreview;
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)
553 columnRendered = true;
554 columnNames.push(cellProperty.name);
557 if (columnRendered) {
558 var cellElement = this._renderPropertyPreviewOrAccessor(table, [rowProperty, cellProperty]);
559 cellElement.classList.add("console-message-nowrap-below");
560 rowValue[cellProperty.name] = cellElement;
563 rows.push([rowProperty.name, rowValue]);
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]]);
575 var dataGridContainer = element.createChild("span");
576 if (!preview.lossless || !flatValues.length) {
577 element.appendChild(this._formatParameter(table, true, false));
578 if (!flatValues.length)
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);
591 * @param {!WebInspector.RemoteObject} output
592 * @param {!Element} elem
594 _formatParameterAsString: function(output, elem)
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("\"");
608 * @param {!WebInspector.RemoteObject} output
609 * @param {!Element} elem
611 _formatParameterAsError: function(output, elem)
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)
622 span.removeChildren();
623 detailedLink.remove();
624 span.appendChild(WebInspector.linkifyStringAsFragment(text));
627 detailedLink._showDetailedForTest = showDetailed.bind(null, new MouseEvent('click'));
628 detailedLink.addEventListener("click", showDetailed, false);
633 * @param {!WebInspector.RemoteObject} array
634 * @param {!Element} elem
635 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
637 _printArray: function(array, elem, properties)
640 this._formatParameterAsObject(array, elem, false);
645 for (var i = 0; i < properties.length; ++i) {
646 var property = properties[i];
647 var name = property.name;
651 elements[name] = this._formatAsAccessorProperty(array, [name], true);
652 else if (property.value)
653 elements[name] = this._formatAsArrayEntry(property.value);
656 elem.createTextChild("[");
657 var lastNonEmptyIndex = -1;
659 function appendUndefined(elem, index)
661 if (index - lastNonEmptyIndex <= 1)
663 var span = elem.createChild("span", "object-value-undefined");
664 span.textContent = WebInspector.UIString("undefined × %d", index - lastNonEmptyIndex - 1);
667 var length = array.arrayLength();
668 for (var i = 0; i < length; ++i) {
669 var element = elements[i];
673 if (i - lastNonEmptyIndex > 1) {
674 appendUndefined(elem, i);
675 elem.createTextChild(", ");
678 elem.appendChild(element);
679 lastNonEmptyIndex = i;
681 elem.createTextChild(", ");
683 appendUndefined(elem, length);
685 elem.createTextChild("]");
686 elem.addEventListener("contextmenu", this._contextMenuEventFired.bind(this, array), false);
690 * @param {!WebInspector.RemoteObject} output
693 _formatAsArrayEntry: function(output)
695 // Prevent infinite expansion of cross-referencing arrays.
696 return this._formatParameter(output, output.subtype === "array", false);
700 * @param {?WebInspector.RemoteObject} object
701 * @param {!Array.<string>} propertyPath
702 * @param {boolean} isArrayEntry
705 _formatAsAccessorProperty: function(object, propertyPath, isArrayEntry)
707 var rootElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(object, propertyPath, onInvokeGetterClick.bind(this));
710 * @param {?WebInspector.RemoteObject} result
711 * @param {boolean=} wasThrown
712 * @this {WebInspector.ConsoleViewMessage}
714 function onInvokeGetterClick(result, wasThrown)
718 rootElement.removeChildren();
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));
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);
735 description = result.description.trimEnd(maxLength);
737 rootElement.appendChild(this._previewFormatter.renderPropertyPreview(type, subtype, description));
745 * @param {string} format
746 * @param {!Array.<string>} parameters
747 * @param {!Element} formattedResult
749 _formatWithSubstitutionString: function(format, parameters, formattedResult)
754 * @param {boolean} force
755 * @param {!WebInspector.RemoteObject} obj
757 * @this {WebInspector.ConsoleViewMessage}
759 function parameterFormatter(force, obj)
761 return this._formatParameter(obj, force, false);
764 function stringFormatter(obj)
766 return obj.description;
769 function floatFormatter(obj)
771 if (typeof obj.value !== "number")
776 function integerFormatter(obj)
778 if (typeof obj.value !== "number")
780 return Math.floor(obj.value);
783 function bypassFormatter(obj)
785 return (obj instanceof Node) ? obj : "";
788 var currentStyle = null;
789 function styleFormatter(obj)
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];
802 function isWhitelistedProperty(property)
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]))
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)
830 if (b instanceof Node)
832 else if (typeof b !== "undefined") {
833 var toAppend = WebInspector.linkifyStringAsFragment(String(b));
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]);
842 a.appendChild(toAppend);
848 * @param {!Element} element
850 function applyCurrentStyle(element)
852 for (var key in currentStyle)
853 element.style[key] = currentStyle[key];
856 // String.format does treat formattedResult like a Builder, result is an object.
857 return String.format(format, parameters, formatters, formattedResult, append);
863 matchesFilterRegex: function(regexObject)
865 regexObject.lastIndex = 0;
866 var text = this.searchableElement().deepTextContent();
867 if (this._anchorElement)
868 text += " " + this._anchorElement.textContent;
869 return regexObject.test(text);
873 * @param {boolean} show
875 updateTimestamp: function(show)
877 if (!this._formattedMessage)
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);
888 if (!show && this.timestampElement) {
889 this.timestampElement.remove();
890 delete this.timestampElement;
897 nestingLevel: function()
899 return this._nestingLevel;
902 resetCloseGroupDecorationCount: function()
904 if (!this._closeGroupDecorationCount)
906 this._closeGroupDecorationCount = 0;
907 this._updateCloseGroupDecorations();
910 incrementCloseGroupDecorationCount: function()
912 ++this._closeGroupDecorationCount;
913 this._updateCloseGroupDecorations();
916 _updateCloseGroupDecorations: function()
918 if (!this._nestingLevelMarkers)
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);
929 contentElement: function()
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;
953 toMessageElement: function()
955 if (this._wrapperElement)
956 return this._wrapperElement;
958 this._wrapperElement = createElement("div");
959 this.updateMessageElement();
960 return this._wrapperElement;
963 updateMessageElement: function()
965 if (!this._wrapperElement)
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");
981 case WebInspector.ConsoleMessage.MessageLevel.Debug:
982 this._wrapperElement.classList.add("console-debug-level");
984 case WebInspector.ConsoleMessage.MessageLevel.Warning:
985 this._wrapperElement.classList.add("console-warning-level");
987 case WebInspector.ConsoleMessage.MessageLevel.Error:
988 this._wrapperElement.classList.add("console-error-level");
990 case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
991 this._wrapperElement.classList.add("console-revokedError-level");
993 case WebInspector.ConsoleMessage.MessageLevel.Info:
994 this._wrapperElement.classList.add("console-info-level");
998 this._wrapperElement.appendChild(this.contentElement());
1002 * @param {!TreeElement} parentTreeElement
1004 _populateStackTraceTreeElement: function(parentTreeElement)
1006 var target = this._target();
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);
1016 resetIncrementRepeatCount: function()
1018 this._repeatCount = 1;
1019 if (!this._repeatCountElement)
1022 this._repeatCountElement.remove();
1023 delete this._repeatCountElement;
1026 incrementRepeatCount: function()
1028 this._repeatCount++;
1029 this._showRepeatCountElement();
1032 _showRepeatCountElement: function()
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");
1044 this._repeatCountElement.textContent = this._repeatCount;
1051 toString: function()
1054 switch (this._message.source) {
1055 case WebInspector.ConsoleMessage.MessageSource.XML:
1056 sourceString = "XML";
1058 case WebInspector.ConsoleMessage.MessageSource.JS:
1059 sourceString = "JavaScript";
1061 case WebInspector.ConsoleMessage.MessageSource.Network:
1062 sourceString = "Network";
1064 case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
1065 sourceString = "ConsoleAPI";
1067 case WebInspector.ConsoleMessage.MessageSource.Storage:
1068 sourceString = "Storage";
1070 case WebInspector.ConsoleMessage.MessageSource.AppCache:
1071 sourceString = "AppCache";
1073 case WebInspector.ConsoleMessage.MessageSource.Rendering:
1074 sourceString = "Rendering";
1076 case WebInspector.ConsoleMessage.MessageSource.CSS:
1077 sourceString = "CSS";
1079 case WebInspector.ConsoleMessage.MessageSource.Security:
1080 sourceString = "Security";
1082 case WebInspector.ConsoleMessage.MessageSource.Other:
1083 sourceString = "Other";
1088 switch (this._message.type) {
1089 case WebInspector.ConsoleMessage.MessageType.Log:
1092 case WebInspector.ConsoleMessage.MessageType.Dir:
1095 case WebInspector.ConsoleMessage.MessageType.DirXML:
1096 typeString = "Dir XML";
1098 case WebInspector.ConsoleMessage.MessageType.Trace:
1099 typeString = "Trace";
1101 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
1102 case WebInspector.ConsoleMessage.MessageType.StartGroup:
1103 typeString = "Start Group";
1105 case WebInspector.ConsoleMessage.MessageType.EndGroup:
1106 typeString = "End Group";
1108 case WebInspector.ConsoleMessage.MessageType.Assert:
1109 typeString = "Assert";
1111 case WebInspector.ConsoleMessage.MessageType.Result:
1112 typeString = "Result";
1114 case WebInspector.ConsoleMessage.MessageType.Profile:
1115 case WebInspector.ConsoleMessage.MessageType.ProfileEnd:
1116 typeString = "Profiling";
1121 switch (this._message.level) {
1122 case WebInspector.ConsoleMessage.MessageLevel.Log:
1123 levelString = "Log";
1125 case WebInspector.ConsoleMessage.MessageLevel.Warning:
1126 levelString = "Warning";
1128 case WebInspector.ConsoleMessage.MessageLevel.Debug:
1129 levelString = "Debug";
1131 case WebInspector.ConsoleMessage.MessageLevel.Error:
1132 levelString = "Error";
1134 case WebInspector.ConsoleMessage.MessageLevel.RevokedError:
1135 levelString = "RevokedError";
1137 case WebInspector.ConsoleMessage.MessageLevel.Info:
1138 levelString = "Info";
1142 return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage().textContent + "\n" + this._message.url + " line " + this._message.line;
1147 return this._message.messageText;
1151 * @param {?RegExp} regex
1153 setSearchRegex: function(regex)
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)
1163 var text = this.searchableElement().deepTextContent();
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);
1177 searchRegex: function()
1179 return this._searchRegex;
1185 searchCount: function()
1187 return this._searchHighlightNodes.length;
1191 * @return {!Element}
1193 searchHighlightNode: function(index)
1195 return this._searchHighlightNodes[index];
1199 * @return {!Element}
1201 searchableElement: function()
1203 this.formattedMessage();
1204 return this._messageElement;
1208 * @param {string} string
1209 * @return {?Element}
1211 _tryFormatAsError: function(string)
1214 * @param {string} prefix
1216 function startsWith(prefix)
1218 return string.startsWith(prefix);
1221 var errorPrefixes = ["EvalError", "ReferenceError", "SyntaxError", "TypeError", "RangeError", "Error", "URIError"];
1222 var target = this._target();
1223 if (!target || !errorPrefixes.some(startsWith))
1225 var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
1229 var lines = string.split("\n");
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)
1238 if (!isCallFrameLine)
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))
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);
1256 var parsed = splitResult.url.asParsedURL();
1260 else if (debuggerModel.scriptsForSourceURL(splitResult.url).length)
1261 url = splitResult.url;
1262 else if (splitResult.url === "<anonymous>")
1267 links.push({url: url, positionLeft: position + left, positionRight: position + right, lineNumber: splitResult.lineNumber, columnNumber: splitResult.columnNumber});
1273 var formattedResult = createElement("span");
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;
1281 if (start != string.length)
1282 formattedResult.appendChild(WebInspector.linkifyStringAsFragment(string.substring(start)));
1284 return formattedResult;
1290 * @extends {WebInspector.ConsoleViewMessage}
1291 * @param {!WebInspector.ConsoleMessage} consoleMessage
1292 * @param {!WebInspector.Linkifier} linkifier
1293 * @param {number} nestingLevel
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 = {
1304 * @param {boolean} collapsed
1306 setCollapsed: function(collapsed)
1308 this._collapsed = collapsed;
1309 if (this._wrapperElement)
1310 this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1316 collapsed: function()
1318 return this._collapsed;
1323 * @return {!Element}
1325 toMessageElement: function()
1327 if (!this._wrapperElement) {
1328 WebInspector.ConsoleViewMessage.prototype.toMessageElement.call(this);
1329 this._wrapperElement.classList.toggle("collapsed", this._collapsed);
1331 return this._wrapperElement;
1334 __proto__: WebInspector.ConsoleViewMessage.prototype