e58f31c593bebf21972f18bcefd6323a9c091319
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / ConsoleMessageImpl.js
1 /*
2  * Copyright (C) 2011 Google Inc.  All rights reserved.
3  * Copyright (C) 2007, 2008, 2013 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 Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 WebInspector.ConsoleMessageImpl = function(source, level, message, linkifier, type, url, line, column, repeatCount, parameters, stackTrace, request)
32 {
33     WebInspector.ConsoleMessage.call(this, source, level, url, line, column, repeatCount);
34
35     this._linkifier = linkifier;
36     this.type = type || WebInspector.ConsoleMessage.MessageType.Log;
37     this._messageText = message;
38     this._parameters = parameters;
39     this._stackTrace = stackTrace;
40     this._request = request;
41
42     this._customFormatters = {
43         "object": this._formatParameterAsObject,
44         "error": this._formatParameterAsObject,
45         "map": this._formatParameterAsObject,
46         "set": this._formatParameterAsObject,
47         "weakmap": this._formatParameterAsObject,
48         "array":  this._formatParameterAsArray,
49         "node":   this._formatParameterAsNode,
50         "string": this._formatParameterAsString
51     };
52 };
53
54 WebInspector.ConsoleMessageImpl.prototype = {
55
56     enforcesClipboardPrefixString: true,
57
58     _formatMessage: function()
59     {
60         this._formattedMessage = document.createElement("span");
61         this._formattedMessage.className = "console-message-text source-code";
62
63         var messageText;
64         if (this.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
65             switch (this.type) {
66                 case WebInspector.ConsoleMessage.MessageType.Trace:
67                     messageText = document.createTextNode("console.trace()");
68                     break;
69                 case WebInspector.ConsoleMessage.MessageType.Assert:
70                     var args = [WebInspector.UIString("Assertion failed:")];
71                     if (this._parameters)
72                         args = args.concat(this._parameters);
73                     messageText = this._format(args);
74                     break;
75                 case WebInspector.ConsoleMessage.MessageType.Dir:
76                     var obj = this._parameters ? this._parameters[0] : undefined;
77                     var args = ["%O", obj];
78                     messageText = this._format(args);
79                     break;
80                 default:
81                     var args = this._parameters || [this._messageText];
82                     messageText = this._format(args);
83             }
84         } else if (this.source === WebInspector.ConsoleMessage.MessageSource.Network) {
85             if (this._request) {
86                 this._stackTrace = this._request.stackTrace;
87                 if (this._request.initiator && this._request.initiator.url) {
88                     this.url = this._request.initiator.url;
89                     this.line = this._request.initiator.lineNumber;
90                 }
91                 messageText = document.createElement("span");
92                 if (this.level === WebInspector.ConsoleMessage.MessageLevel.Error) {
93                     messageText.appendChild(document.createTextNode(this._request.requestMethod + " "));
94                     messageText.appendChild(WebInspector.linkifyRequestAsNode(this._request));
95                     if (this._request.failed)
96                         messageText.appendChild(document.createTextNode(" " + this._request.localizedFailDescription));
97                     else
98                         messageText.appendChild(document.createTextNode(" " + this._request.statusCode + " (" + this._request.statusText + ")"));
99                 } else {
100                     var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(this._messageText, WebInspector.linkifyRequestAsNode.bind(null, this._request, ""));
101                     messageText.appendChild(fragment);
102                 }
103             } else {
104                 if (this.url) {
105                     var anchor = WebInspector.linkifyURLAsNode(this.url, this.url, "console-message-url");
106                     this._formattedMessage.appendChild(anchor);
107                 }
108                 messageText = this._format([this._messageText]);
109             }
110         } else {
111             var args = this._parameters || [this._messageText];
112             messageText = this._format(args);
113         }
114
115         if (this.source !== WebInspector.ConsoleMessage.MessageSource.Network || this._request) {
116             var firstNonNativeCallFrame = this._firstNonNativeCallFrame();
117             if (firstNonNativeCallFrame) {
118                 var urlElement = this._linkifyCallFrame(firstNonNativeCallFrame);
119                 this._formattedMessage.appendChild(urlElement);
120             } else if (this.url && !this._shouldHideURL(this.url)) {
121                 var urlElement = this._linkifyLocation(this.url, this.line, this.column);
122                 this._formattedMessage.appendChild(urlElement);
123             }
124         }
125
126         this._formattedMessage.appendChild(messageText);
127
128         if (this._shouldDumpStackTrace()) {
129             var ol = document.createElement("ol");
130             ol.className = "outline-disclosure";
131             var treeOutline = new TreeOutline(ol);
132
133             var content = this._formattedMessage;
134             var root = new TreeElement(content, null, true);
135             content.treeElementForTest = root;
136             treeOutline.appendChild(root);
137             if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
138                 root.expand();
139
140             this._populateStackTraceTreeElement(root);
141             this._formattedMessage = ol;
142         }
143
144         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
145         this._message = messageText.textContent;
146     },
147
148     _shouldDumpStackTrace: function()
149     {
150         return !!this._stackTrace && this._stackTrace.length && (this.source === WebInspector.ConsoleMessage.MessageSource.Network || this.level === WebInspector.ConsoleMessage.MessageLevel.Error || this.type === WebInspector.ConsoleMessage.MessageType.Trace);
151     },
152
153     _shouldHideURL: function(url)
154     {
155         return url === "undefined" || url === "[native code]";
156     },
157
158     _firstNonNativeCallFrame: function()
159     {
160         if (!this._stackTrace)
161             return null;
162
163         for (var i = 0; i < this._stackTrace.length; i++) {
164             var frame = this._stackTrace[i];
165             if (!frame.url || frame.url === "[native code]")
166                 continue;
167             return frame;
168         }
169
170         return null;
171     },
172
173     get message()
174     {
175         // force message formatting
176         var formattedMessage = this.formattedMessage;
177         return this._message;
178     },
179
180     get formattedMessage()
181     {
182         if (!this._formattedMessage)
183             this._formatMessage();
184         return this._formattedMessage;
185     },
186
187     _linkifyLocation: function(url, lineNumber, columnNumber)
188     {
189         // ConsoleMessage stack trace line numbers are one-based.
190         lineNumber = lineNumber ? lineNumber - 1 : 0;
191
192         return WebInspector.linkifyLocation(url, lineNumber, columnNumber, "console-message-url");
193     },
194
195     _linkifyCallFrame: function(callFrame)
196     {
197         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
198     },
199
200     isErrorOrWarning: function()
201     {
202         return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
203     },
204
205     _format: function(parameters)
206     {
207         // This node is used like a Builder. Values are continually appended onto it.
208         var formattedResult = document.createElement("span");
209         if (!parameters.length)
210             return formattedResult;
211
212         // Formatting code below assumes that parameters are all wrappers whereas frontend console
213         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
214         for (var i = 0; i < parameters.length; ++i) {
215             // FIXME: Only pass runtime wrappers here.
216             if (parameters[i] instanceof WebInspector.RemoteObject)
217                 continue;
218
219             if (typeof parameters[i] === "object")
220                 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
221             else
222                 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
223         }
224
225         // There can be string log and string eval result. We distinguish between them based on message type.
226         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
227
228         if (shouldFormatMessage) {
229             // Multiple parameters with the first being a format string. Save unused substitutions.
230             var result = this._formatWithSubstitutionString(parameters, formattedResult);
231             parameters = result.unusedSubstitutions;
232             if (parameters.length)
233                 formattedResult.appendChild(document.createTextNode(" "));
234         }
235
236         if (this.type === WebInspector.ConsoleMessage.MessageType.Table) {
237             formattedResult.appendChild(this._formatParameterAsTable(parameters));
238             return formattedResult;
239         }
240
241         // Single parameter, or unused substitutions from above.
242         for (var i = 0; i < parameters.length; ++i) {
243             // Inline strings when formatting.
244             if (shouldFormatMessage && parameters[i].type === "string") {
245                 var span = document.createElement("span");
246                 span.classList.add("type-string");
247                 span.textContent = parameters[i].description;
248                 formattedResult.appendChild(span);
249             } else
250                 formattedResult.appendChild(this._formatParameter(parameters[i], false, true));
251
252             if (i < parameters.length - 1 && !this._isExpandable(parameters[i]))
253                 formattedResult.appendChild(document.createTextNode(" "));
254
255         }
256         return formattedResult;
257     },
258
259     _isExpandable: function(remoteObject) {
260         if (!remoteObject)
261             return false;
262
263         if (remoteObject.hasChildren && remoteObject.preview && remoteObject.preview.lossless)
264             return false;
265
266         return remoteObject.hasChildren;
267     },
268
269     _formatParameter: function(output, forceObjectFormat, includePreview)
270     {
271         var type;
272         if (forceObjectFormat)
273             type = "object";
274         else if (output instanceof WebInspector.RemoteObject)
275             type = output.subtype || output.type;
276         else
277             type = typeof output;
278
279         var formatter = this._customFormatters[type];
280         if (!formatter) {
281             formatter = this._formatParameterAsValue;
282             output = output.description;
283         }
284
285         var span = document.createElement("span");
286         span.className = "console-formatted-" + type + " source-code";
287
288         if (this._isExpandable(output))
289             span.classList.add("expandable");
290
291         formatter.call(this, output, span, includePreview);
292         return span;
293     },
294
295     _formatParameterAsValue: function(val, elem)
296     {
297         elem.appendChild(document.createTextNode(val));
298     },
299
300     _formatParameterAsObject: function(obj, elem, includePreview)
301     {
302         var titleElement = document.createElement("span");
303         if (includePreview && obj.preview) {
304             titleElement.classList.add("console-object-preview");
305
306             // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
307             // Runtime.ObjectPreview. Copy them over from the RemoteObject.
308             var preview = obj.preview;
309             if (!preview.type) {
310                 preview.type = obj.type;
311                 preview.subtype = obj.subtype;
312                 preview.description = obj.description;
313             }
314
315             var lossless = this._appendPreview(titleElement, preview);
316             if (lossless) {
317                 titleElement.classList.add("console-object-preview-lossless");
318                 elem.appendChild(titleElement);
319                 return;
320             }
321         } else
322             titleElement.appendChild(document.createTextNode(obj.description || ""));
323
324         var section = new WebInspector.ObjectPropertiesSection(obj, titleElement);
325         elem.appendChild(section.element);
326     },
327
328     _appendPreview: function(element, preview)
329     {
330         if (preview.type === "object" && preview.subtype !== "null" && preview.subtype !== "array") {
331             var previewObjectNameElement = document.createElement("span");
332             previewObjectNameElement.classList.add("console-object-preview-name");
333             if (preview.description === "Object")
334                 previewObjectNameElement.classList.add("console-object-preview-name-Object");
335
336             previewObjectNameElement.textContent = preview.description + " ";
337             element.appendChild(previewObjectNameElement);
338         }
339
340         var bodyElement = element.createChild("span", "console-object-preview-body");
341         if (preview.entries)
342             return this._appendEntryPreviews(bodyElement, preview);
343         if (preview.properties)
344             return this._appendPropertyPreviews(bodyElement, preview);
345         return this._appendValuePreview(bodyElement, preview);
346     },
347
348     _appendEntryPreviews: function(element, preview)
349     {
350         var lossless = preview.lossless && !preview.properties.length;
351
352         element.appendChild(document.createTextNode("{"));
353
354         for (var i = 0; i < preview.entries.length; ++i) {
355             if (i > 0)
356                 element.appendChild(document.createTextNode(", "));
357
358             var entry = preview.entries[i];
359             if (entry.key) {
360                 this._appendPreview(element, entry.key);
361                 element.appendChild(document.createTextNode(" => "));
362             }
363
364             this._appendPreview(element, entry.value);
365         }
366
367         if (preview.overflow)
368             element.createChild("span").textContent = "\u2026";
369         element.appendChild(document.createTextNode("}"));
370
371         return lossless;
372     },
373
374     _appendPropertyPreviews: function(element, preview)
375     {
376         var isArray = preview.subtype === "array";
377
378         element.appendChild(document.createTextNode(isArray ? "[" : "{"));
379
380         for (var i = 0; i < preview.properties.length; ++i) {
381             var property = preview.properties[i];
382
383             // FIXME: Better handle getter/setter accessors. Should we show getters in previews?
384             if (property.type === "accessor")
385                 continue;
386
387             // Constructor name is often already visible, so don't show it as a property.
388             if (property.name === "constructor")
389                 continue;
390
391             if (i > 0)
392                 element.appendChild(document.createTextNode(", "));
393
394             if (!isArray || property.name != i) {
395                 element.createChild("span", "name").textContent = property.name;
396                 element.appendChild(document.createTextNode(": "));
397             }
398
399             element.appendChild(this._propertyPreviewElement(property));
400         }
401
402         if (preview.overflow)
403             element.createChild("span").textContent = "\u2026";
404
405         element.appendChild(document.createTextNode(isArray ? "]" : "}"));
406
407         return preview.lossless;
408     },
409
410     _propertyPreviewElement: function(property)
411     {
412         var span = document.createElement("span");
413         span.classList.add("console-formatted-" + property.type);
414
415         if (property.type === "string") {
416             span.textContent = "\"" + property.value.replace(/\n/g, "\u21B5") + "\"";
417             return span;
418         }
419
420         if (property.type === "function") {
421             span.textContent = "function";
422             return span;
423         }
424
425         if (property.type === "object") {
426             if (property.subtype === "node")
427                 span.classList.add("console-formatted-preview-node");
428             else if (property.subtype === "regexp")
429                 span.classList.add("console-formatted-regexp");
430         }
431
432         span.textContent = property.value;
433         return span;
434     },
435
436     _appendValuePreview: function(element, preview)
437     {
438         element.appendChild(document.createTextNode(preview.description));
439     },
440
441     _formatParameterAsNode: function(object, elem)
442     {
443         function printNode(nodeId)
444         {
445             if (!nodeId) {
446                 // Sometimes DOM is loaded after the sync message is being formatted, so we get no
447                 // nodeId here. So we fall back to object formatting here.
448                 this._formatParameterAsObject(object, elem, false);
449                 return;
450             }
451             var treeOutline = new WebInspector.DOMTreeOutline(false, false, true);
452             treeOutline.setVisible(true);
453             treeOutline.rootDOMNode = WebInspector.domTreeManager.nodeForId(nodeId);
454             treeOutline.element.classList.add("outline-disclosure");
455             if (!treeOutline.children[0].hasChildren)
456                 treeOutline.element.classList.add("single-node");
457             elem.appendChild(treeOutline.element);
458         }
459         object.pushNodeToFrontend(printNode.bind(this));
460     },
461
462     _formatParameterAsArray: function(arr, elem)
463     {
464         // FIXME: Array previews look poor. Keep doing what we currently do for arrays.
465         arr.getOwnProperties(this._printArray.bind(this, arr, elem));
466     },
467
468     _userProvidedColumnNames: function(columnNamesArgument)
469     {
470         if (!columnNamesArgument)
471             return null;
472
473         var remoteObject = WebInspector.RemoteObject.fromPayload(columnNamesArgument);
474
475         // Single primitive argument.
476         if (remoteObject.type === "string" || remoteObject.type === "number")
477             return [String(columnNamesArgument.value)];
478
479         // Ignore everything that is not an array with property previews.
480         if (remoteObject.type !== "object" || remoteObject.subtype !== "array" || !remoteObject.preview || !remoteObject.preview.properties)
481             return null;
482
483         // Array. Look into the preview and get string values.
484         var extractedColumnNames = [];
485         for (var propertyPreview of remoteObject.preview.properties) {
486             if (propertyPreview.type === "string" || propertyPreview.type === "number")
487                 extractedColumnNames.push(String(propertyPreview.value));
488         }
489
490         return extractedColumnNames.length ? extractedColumnNames : null;
491     },
492
493     _formatParameterAsTable: function(parameters)
494     {
495         var element = document.createElement("span");
496         var table = parameters[0];
497         if (!table || !table.preview)
498             return element;
499
500         var rows = [];
501         var columnNames = [];
502         var flatValues = [];
503         var preview = table.preview;
504         var userProvidedColumnNames = false;
505
506         // User provided columnNames.
507         var extractedColumnNames = this._userProvidedColumnNames(parameters[1]);
508         if (extractedColumnNames) {
509             userProvidedColumnNames = true;
510             columnNames = extractedColumnNames;
511         }
512
513         // Check first for valuePreviews in the properties meaning this was an array of objects.
514         for (var i = 0; i < preview.properties.length; ++i) {
515             var rowProperty = preview.properties[i];
516             var rowPreview = rowProperty.valuePreview;
517             if (!rowPreview)
518                 continue;
519
520             var rowValue = {};
521             const maxColumnsToRender = 10;
522             for (var j = 0; j < rowPreview.properties.length; ++j) {
523                 var cellProperty = rowPreview.properties[j];
524                 var columnRendered = columnNames.contains(cellProperty.name);
525                 if (!columnRendered) {
526                     if (userProvidedColumnNames || columnNames.length === maxColumnsToRender)
527                         continue;
528                     columnRendered = true;
529                     columnNames.push(cellProperty.name);
530                 }
531
532                 rowValue[cellProperty.name] = this._propertyPreviewElement(cellProperty);
533             }
534             rows.push([rowProperty.name, rowValue]);
535         }
536
537         // If there were valuePreviews, convert to a flat list.
538         if (rows.length) {
539             const emDash = "\u2014";
540             columnNames.unshift(WebInspector.UIString("(Index)"));
541             for (var i = 0; i < rows.length; ++i) {
542                 var rowName = rows[i][0];
543                 var rowValue = rows[i][1];
544                 flatValues.push(rowName);
545                 for (var j = 1; j < columnNames.length; ++j) {
546                     var columnName = columnNames[j];
547                     if (!(columnName in rowValue))
548                         flatValues.push(emDash);
549                     else
550                         flatValues.push(rowValue[columnName]);
551                 }
552             }
553         }
554
555         // If there were no value Previews, then check for an array of values.
556         if (!flatValues.length) {
557             for (var i = 0; i < preview.properties.length; ++i) {
558                 var rowProperty = preview.properties[i];
559                 if (!("value" in rowProperty))
560                     continue;
561
562                 if (!columnNames.length) {
563                     columnNames.push(WebInspector.UIString("Index"));
564                     columnNames.push(WebInspector.UIString("Value"));
565                 }
566
567                 flatValues.push(rowProperty.name);
568                 flatValues.push(this._propertyPreviewElement(rowProperty));
569             }
570         }
571
572         // If lossless or not table data, output the object so full data can be gotten.
573         if (!preview.lossless || !flatValues.length) {
574             element.appendChild(this._formatParameter(table, true, false));
575             if (!flatValues.length)
576                 return element;
577         }
578
579         var dataGridContainer = element.createChild("span");
580         var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues);
581         dataGrid.element.classList.add("inline");
582         dataGridContainer.appendChild(dataGrid.element);
583
584         return element;
585     },
586
587     _formatParameterAsString: function(output, elem)
588     {
589         var span = document.createElement("span");
590         span.className = "console-formatted-string source-code";
591         span.appendChild(document.createTextNode("\""));
592         span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
593         span.appendChild(document.createTextNode("\""));
594
595         elem.classList.remove("console-formatted-string");        
596         elem.appendChild(span);
597     },
598
599     _printArray: function(array, elem, properties)
600     {
601         if (!properties)
602             return;
603
604         var elements = [];
605         for (var i = 0; i < properties.length; ++i) {
606             var property = properties[i];
607             var name = property.name;
608             if (!isNaN(name))
609                 elements[name] = this._formatAsArrayEntry(property.value);
610         }
611
612         elem.appendChild(document.createTextNode("["));
613         var lastNonEmptyIndex = -1;
614
615         function appendUndefined(elem, index)
616         {
617             if (index - lastNonEmptyIndex <= 1)
618                 return;
619             var span = elem.createChild("span", "console-formatted-undefined");
620             span.textContent = WebInspector.UIString("undefined × %d").format(index - lastNonEmptyIndex - 1);
621         }
622
623         var length = array.arrayLength();
624         for (var i = 0; i < length; ++i) {
625             var element = elements[i];
626             if (!element)
627                 continue;
628
629             if (i - lastNonEmptyIndex > 1) {
630                 appendUndefined(elem, i);
631                 elem.appendChild(document.createTextNode(", "));
632             }
633
634             elem.appendChild(element);
635             lastNonEmptyIndex = i;
636             if (i < length - 1)
637                 elem.appendChild(document.createTextNode(", "));
638         }
639         appendUndefined(elem, length);
640
641         elem.appendChild(document.createTextNode("]"));
642     },
643
644     _formatAsArrayEntry: function(output)
645     {
646         // Prevent infinite expansion of cross-referencing arrays.
647         return this._formatParameter(output, output.subtype && output.subtype === "array", false);
648     },
649
650     _formatWithSubstitutionString: function(parameters, formattedResult)
651     {
652         var formatters = {};
653
654         function parameterFormatter(force, obj)
655         {
656             return this._formatParameter(obj, force, false);
657         }
658
659         function stringFormatter(obj)
660         {
661             return obj.description;
662         }
663
664         function floatFormatter(obj)
665         {
666             if (typeof obj.value !== "number")
667                 return parseFloat(obj.description);
668             return obj.value;
669         }
670
671         function integerFormatter(obj)
672         {
673             if (typeof obj.value !== "number")
674                 return parseInt(obj.description);
675             return Math.floor(obj.value);
676         }
677
678         var currentStyle = null;
679         function styleFormatter(obj)
680         {
681             currentStyle = {};
682             var buffer = document.createElement("span");
683             buffer.setAttribute("style", obj.description);
684             for (var i = 0; i < buffer.style.length; i++) {
685                 var property = buffer.style[i];
686                 if (isWhitelistedProperty(property))
687                     currentStyle[property] = buffer.style[property];
688             }
689         }
690
691         function isWhitelistedProperty(property)
692         {
693             var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
694             for (var i = 0; i < prefixes.length; i++) {
695                 if (property.startsWith(prefixes[i]))
696                     return true;
697             }
698             return false;
699         }
700
701         // Firebug uses %o for formatting objects.
702         formatters.o = parameterFormatter.bind(this, false);
703         formatters.s = stringFormatter;
704         formatters.f = floatFormatter;
705
706         // Firebug allows both %i and %d for formatting integers.
707         formatters.i = integerFormatter;
708         formatters.d = integerFormatter;
709
710         // Firebug uses %c for styling the message.
711         formatters.c = styleFormatter;
712
713         // Support %O to force object formatting, instead of the type-based %o formatting.
714         formatters.O = parameterFormatter.bind(this, true);
715
716         function append(a, b)
717         {
718             if (b instanceof Node)
719                 a.appendChild(b);
720             else if (b) {
721                 var toAppend = WebInspector.linkifyStringAsFragment(b.toString());
722                 if (currentStyle) {
723                     var wrapper = document.createElement("span");
724                     for (var key in currentStyle)
725                         wrapper.style[key] = currentStyle[key];
726                     wrapper.appendChild(toAppend);
727                     toAppend = wrapper;
728                 }
729                 var span = document.createElement("span");
730                 span.className = "type-string";
731                 span.appendChild(toAppend);
732                 a.appendChild(span);
733             }
734             return a;
735         }
736
737         // String.format does treat formattedResult like a Builder, result is an object.
738         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
739     },
740
741     decorateMessageElement: function(element)
742     {
743         if (this._element)
744             return this._element;
745
746         element.message = this;
747         element.classList.add("console-message");
748
749         this._element = element;
750
751         switch (this.level) {
752             case WebInspector.ConsoleMessage.MessageLevel.Tip:
753                 element.classList.add("console-tip-level");
754                 element.setAttribute("data-labelprefix", WebInspector.UIString("Tip: "));
755                 break;
756             case WebInspector.ConsoleMessage.MessageLevel.Log:
757                 element.classList.add("console-log-level");
758                 element.setAttribute("data-labelprefix", WebInspector.UIString("Log: "));
759                 break;
760             case WebInspector.ConsoleMessage.MessageLevel.Debug:
761                 element.classList.add("console-debug-level");
762                 element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: "));
763                 break;
764             case WebInspector.ConsoleMessage.MessageLevel.Warning:
765                 element.classList.add("console-warning-level");
766                 element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: "));
767                 break;
768             case WebInspector.ConsoleMessage.MessageLevel.Error:
769                 element.classList.add("console-error-level");
770                 element.setAttribute("data-labelprefix", WebInspector.UIString("Error: "));
771                 break;
772         }
773
774         if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
775             element.classList.add("console-group-title");
776
777         element.appendChild(this.formattedMessage);
778
779         if (this.repeatCount > 1)
780             this.updateRepeatCount();
781
782         return element;
783     },
784
785     toMessageElement: function()
786     {
787         if (this._element)
788             return this._element;
789
790         var element = document.createElement("div");
791
792         return this.decorateMessageElement(element);
793     },
794
795     _populateStackTraceTreeElement: function(parentTreeElement)
796     {
797         for (var i = 0; i < this._stackTrace.length; i++) {
798             var frame = this._stackTrace[i];
799
800             var content = document.createElement("div");
801             var messageTextElement = document.createElement("span");
802             messageTextElement.className = "console-message-text source-code";
803             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
804             messageTextElement.appendChild(document.createTextNode(functionName));
805             content.appendChild(messageTextElement);
806
807             if (frame.url && !this._shouldHideURL(frame.url)) {
808                 var urlElement = this._linkifyCallFrame(frame);
809                 content.appendChild(urlElement);
810             }
811
812             var treeElement = new TreeElement(content);
813             parentTreeElement.appendChild(treeElement);
814         }
815     },
816
817     updateRepeatCount: function() {
818         if (!this.repeatCountElement) {
819             this.repeatCountElement = document.createElement("span");
820             this.repeatCountElement.className = "bubble";
821
822             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
823         }
824         this.repeatCountElement.textContent = this.repeatCount;
825     },
826
827     toString: function()
828     {
829         var sourceString;
830         switch (this.source) {
831             case WebInspector.ConsoleMessage.MessageSource.HTML:
832                 sourceString = "HTML";
833                 break;
834             case WebInspector.ConsoleMessage.MessageSource.XML:
835                 sourceString = "XML";
836                 break;
837             case WebInspector.ConsoleMessage.MessageSource.JS:
838                 sourceString = "JS";
839                 break;
840             case WebInspector.ConsoleMessage.MessageSource.Network:
841                 sourceString = "Network";
842                 break;
843             case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
844                 sourceString = "ConsoleAPI";
845                 break;
846             case WebInspector.ConsoleMessage.MessageSource.Other:
847                 sourceString = "Other";
848                 break;
849         }
850
851         var typeString;
852         switch (this.type) {
853             case WebInspector.ConsoleMessage.MessageType.Log:
854                 typeString = "Log";
855                 break;
856             case WebInspector.ConsoleMessage.MessageType.Dir:
857                 typeString = "Dir";
858                 break;
859             case WebInspector.ConsoleMessage.MessageType.DirXML:
860                 typeString = "Dir XML";
861                 break;
862             case WebInspector.ConsoleMessage.MessageType.Trace:
863                 typeString = "Trace";
864                 break;
865             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
866             case WebInspector.ConsoleMessage.MessageType.StartGroup:
867                 typeString = "Start Group";
868                 break;
869             case WebInspector.ConsoleMessage.MessageType.EndGroup:
870                 typeString = "End Group";
871                 break;
872             case WebInspector.ConsoleMessage.MessageType.Assert:
873                 typeString = "Assert";
874                 break;
875             case WebInspector.ConsoleMessage.MessageType.Result:
876                 typeString = "Result";
877                 break;
878         }
879
880         return sourceString + " " + typeString + " " + this.levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
881     },
882
883     get text()
884     {
885         return this._messageText;
886     },
887
888     isEqual: function(msg)
889     {
890         if (!msg)
891             return false;
892
893         if (this._stackTrace) {
894             if (!msg._stackTrace)
895                 return false;
896             var l = this._stackTrace;
897             var r = msg._stackTrace;
898             for (var i = 0; i < l.length; i++) {
899                 if (l[i].url !== r[i].url ||
900                     l[i].functionName !== r[i].functionName ||
901                     l[i].lineNumber !== r[i].lineNumber ||
902                     l[i].columnNumber !== r[i].columnNumber)
903                     return false;
904             }
905         }
906
907         return (this.source === msg.source)
908             && (this.type === msg.type)
909             && (this.level === msg.level)
910             && (this.line === msg.line)
911             && (this.url === msg.url)
912             && (this.message === msg.message)
913             && (this._request === msg._request);
914     },
915
916     get stackTrace()
917     {
918         return this._stackTrace;
919     },
920
921     clone: function()
922     {
923         return WebInspector.ConsoleMessage.create(this.source, this.level, this._messageText, this.type, this.url, this.line, this.column, this.repeatCount, this._parameters, this._stackTrace, this._request);
924     },
925
926     get levelString()
927     {
928         switch (this.level) {
929             case WebInspector.ConsoleMessage.MessageLevel.Tip:
930                 return "Tip";
931             case WebInspector.ConsoleMessage.MessageLevel.Log:
932                 return "Log";
933             case WebInspector.ConsoleMessage.MessageLevel.Warning:
934                 return "Warning";
935             case WebInspector.ConsoleMessage.MessageLevel.Debug:
936                 return "Debug";
937             case WebInspector.ConsoleMessage.MessageLevel.Error:
938                 return "Error";
939         }
940     },
941
942     get clipboardPrefixString()
943     {
944         return "[" + this.levelString + "] ";
945     },
946
947     toClipboardString: function(isPrefixOptional)
948     {
949         var isTrace = this._shouldDumpStackTrace();
950
951         var clipboardString = "";
952         if (this._formattedMessage && !isTrace)
953             clipboardString = this._formattedMessage.querySelector("span").innerText;
954         else
955             clipboardString = this.type === WebInspector.ConsoleMessage.MessageType.Trace ? "console.trace()" : this._message || this._messageText;
956
957         if (!isPrefixOptional || this.enforcesClipboardPrefixString)
958             clipboardString = this.clipboardPrefixString + clipboardString;
959
960         if (isTrace) {
961             this._stackTrace.forEach(function(frame) {
962                 clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)"));
963                 if (frame.url)
964                     clipboardString += " (" + WebInspector.displayNameForURL(frame.url) + ", line " + frame.lineNumber + ")";
965             });
966         } else {
967             var repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : "";
968
969             var urlLine = "";
970             if (this.url) {
971                 var components = [WebInspector.displayNameForURL(this.url), "line " + this.line];
972                 if (repeatString)
973                     components.push(repeatString);
974                 urlLine = " (" + components.join(", ") + ")";
975             } else if (repeatString)
976                 urlLine = " (" + repeatString + ")";
977
978             if (urlLine) {
979                 var lines = clipboardString.split("\n");
980                 lines[0] += urlLine;
981                 clipboardString = lines.join("\n");
982             }
983         }
984
985         return clipboardString;
986     }
987 };
988
989 WebInspector.ConsoleMessageImpl.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;