34c2eaeb496ec8cd061c35dc9107327a7877d19c
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / LegacyConsoleMessageImpl.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.LegacyConsoleMessageImpl = function(source, level, message, linkifier, type, url, line, column, repeatCount, parameters, stackTrace, request)
32 {
33     WebInspector.LegacyConsoleMessage.call(this, source, level, url, line, column, repeatCount);
34
35     this._linkifier = linkifier;
36     this.type = type || WebInspector.LegacyConsoleMessage.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         "iterator": this._formatParameterAsObject,
49         "array":  this._formatParameterAsArray,
50         "node":   this._formatParameterAsNode,
51         "string": this._formatParameterAsString
52     };
53 };
54
55 WebInspector.LegacyConsoleMessageImpl.prototype = {
56
57     enforcesClipboardPrefixString: true,
58
59     _formatMessage: function()
60     {
61         this._formattedMessage = document.createElement("span");
62         this._formattedMessage.className = "console-message-text";
63
64         var messageText;
65         if (this.source === WebInspector.LegacyConsoleMessage.MessageSource.ConsoleAPI) {
66             switch (this.type) {
67                 case WebInspector.LegacyConsoleMessage.MessageType.Trace:
68                     messageText = document.createTextNode("console.trace()");
69                     break;
70                 case WebInspector.LegacyConsoleMessage.MessageType.Assert:
71                     var args = [WebInspector.UIString("Assertion failed:")];
72                     if (this._parameters)
73                         args = args.concat(this._parameters);
74                     messageText = this._format(args);
75                     break;
76                 case WebInspector.LegacyConsoleMessage.MessageType.Dir:
77                     var obj = this._parameters ? this._parameters[0] : undefined;
78                     var args = ["%O", obj];
79                     messageText = this._format(args);
80                     break;
81                 default:
82                     var args = this._parameters || [this._messageText];
83                     messageText = this._format(args);
84             }
85         } else if (this.source === WebInspector.LegacyConsoleMessage.MessageSource.Network) {
86             if (this._request) {
87                 this._stackTrace = this._request.stackTrace;
88                 if (this._request.initiator && this._request.initiator.url) {
89                     this.url = this._request.initiator.url;
90                     this.line = this._request.initiator.lineNumber;
91                 }
92                 messageText = document.createElement("span");
93                 if (this.level === WebInspector.LegacyConsoleMessage.MessageLevel.Error) {
94                     messageText.appendChild(document.createTextNode(this._request.requestMethod + " "));
95                     messageText.appendChild(WebInspector.linkifyRequestAsNode(this._request));
96                     if (this._request.failed)
97                         messageText.appendChild(document.createTextNode(" " + this._request.localizedFailDescription));
98                     else
99                         messageText.appendChild(document.createTextNode(" " + this._request.statusCode + " (" + this._request.statusText + ")"));
100                 } else {
101                     var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(this._messageText, WebInspector.linkifyRequestAsNode.bind(null, this._request, ""));
102                     messageText.appendChild(fragment);
103                 }
104             } else {
105                 if (this.url) {
106                     var anchor = WebInspector.linkifyURLAsNode(this.url, this.url, "console-message-url");
107                     this._formattedMessage.appendChild(anchor);
108                 }
109                 messageText = this._format([this._messageText]);
110             }
111         } else {
112             var args = this._parameters || [this._messageText];
113             messageText = this._format(args);
114         }
115
116         if (this.source !== WebInspector.LegacyConsoleMessage.MessageSource.Network || this._request) {
117             var firstNonNativeCallFrame = this._firstNonNativeCallFrame();
118             if (firstNonNativeCallFrame) {
119                 var urlElement = this._linkifyCallFrame(firstNonNativeCallFrame);
120                 this._formattedMessage.appendChild(urlElement);
121             } else if (this.url && !this._shouldHideURL(this.url)) {
122                 var urlElement = this._linkifyLocation(this.url, this.line, this.column);
123                 this._formattedMessage.appendChild(urlElement);
124             }
125         }
126
127         this._formattedMessage.appendChild(messageText);
128
129         if (this.savedResultIndex) {
130             var savedVariableElement = document.createElement("span");
131             savedVariableElement.className = "console-saved-variable";
132             savedVariableElement.textContent = " = $" + this.savedResultIndex;
133             if (this._objectTree)
134                 this._objectTree.appendTitleSuffix(savedVariableElement);
135             else
136                 this._formattedMessage.appendChild(savedVariableElement);
137         }
138
139         if (this._shouldDumpStackTrace()) {
140             var ol = document.createElement("ol");
141             ol.className = "outline-disclosure";
142             var treeOutline = new WebInspector.TreeOutline(ol);
143
144             var content = this._formattedMessage;
145             var root = new WebInspector.TreeElement(content, null, true);
146             content.treeElementForTest = root;
147             treeOutline.appendChild(root);
148             if (this.type === WebInspector.LegacyConsoleMessage.MessageType.Trace)
149                 root.expand();
150
151             this._populateStackTraceTreeElement(root);
152             this._formattedMessage = ol;
153         }
154
155         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
156         this._message = messageText.textContent;
157     },
158
159     _shouldDumpStackTrace: function()
160     {
161         return !!this._stackTrace && this._stackTrace.length && (this.source === WebInspector.LegacyConsoleMessage.MessageSource.Network || this.level === WebInspector.LegacyConsoleMessage.MessageLevel.Error || this.type === WebInspector.LegacyConsoleMessage.MessageType.Trace);
162     },
163
164     _shouldHideURL: function(url)
165     {
166         return url === "undefined" || url === "[native code]";
167     },
168
169     _firstNonNativeCallFrame: function()
170     {
171         if (!this._stackTrace)
172             return null;
173
174         for (var i = 0; i < this._stackTrace.length; i++) {
175             var frame = this._stackTrace[i];
176             if (!frame.url || frame.url === "[native code]")
177                 continue;
178             return frame;
179         }
180
181         return null;
182     },
183
184     get message()
185     {
186         // force message formatting
187         var formattedMessage = this.formattedMessage;
188         return this._message;
189     },
190
191     get formattedMessage()
192     {
193         if (!this._formattedMessage)
194             this._formatMessage();
195         return this._formattedMessage;
196     },
197
198     _linkifyLocation: function(url, lineNumber, columnNumber)
199     {
200         // ConsoleMessage stack trace line numbers are one-based.
201         lineNumber = lineNumber ? lineNumber - 1 : 0;
202         columnNumber = columnNumber ? columnNumber - 1 : 0;
203
204         return WebInspector.linkifyLocation(url, lineNumber, columnNumber, "console-message-url");
205     },
206
207     _linkifyCallFrame: function(callFrame)
208     {
209         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
210     },
211
212     isErrorOrWarning: function()
213     {
214         return (this.level === WebInspector.LegacyConsoleMessage.MessageLevel.Warning || this.level === WebInspector.LegacyConsoleMessage.MessageLevel.Error);
215     },
216
217     _format: function(parameters)
218     {
219         // This node is used like a Builder. Values are continually appended onto it.
220         var formattedResult = document.createElement("span");
221         if (!parameters.length)
222             return formattedResult;
223
224         // Formatting code below assumes that parameters are all wrappers whereas frontend console
225         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
226         for (var i = 0; i < parameters.length; ++i) {
227             // FIXME: Only pass runtime wrappers here.
228             if (parameters[i] instanceof WebInspector.RemoteObject)
229                 continue;
230
231             if (typeof parameters[i] === "object")
232                 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
233             else
234                 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
235         }
236
237         // There can be string log and string eval result. We distinguish between them based on message type.
238         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.LegacyConsoleMessage.MessageType.Result;
239
240         if (shouldFormatMessage) {
241             // Multiple parameters with the first being a format string. Save unused substitutions.
242             var result = this._formatWithSubstitutionString(parameters, formattedResult);
243             parameters = result.unusedSubstitutions;
244             if (parameters.length)
245                 formattedResult.appendChild(document.createTextNode(" "));
246         }
247
248         if (this.type === WebInspector.LegacyConsoleMessage.MessageType.Table) {
249             formattedResult.appendChild(this._formatParameterAsTable(parameters));
250             return formattedResult;
251         }
252
253         // Single parameter, or unused substitutions from above.
254         for (var i = 0; i < parameters.length; ++i) {
255             // Inline strings when formatting.
256             if (shouldFormatMessage && parameters[i].type === "string") {
257                 var span = document.createElement("span");
258                 span.classList.add("type-string");
259                 span.textContent = parameters[i].description;
260                 formattedResult.appendChild(span);
261             } else
262                 formattedResult.appendChild(this._formatParameter(parameters[i], false));
263
264             if (i < parameters.length - 1 && !this._isExpandable(parameters[i]))
265                 formattedResult.appendChild(document.createTextNode(" "));
266
267         }
268         return formattedResult;
269     },
270
271     _isExpandable: function(remoteObject) {
272         if (!remoteObject)
273             return false;
274
275         if (remoteObject.hasChildren && remoteObject.preview && remoteObject.preview.lossless)
276             return false;
277
278         return remoteObject.hasChildren;
279     },
280
281     _formatParameter: function(output, forceObjectFormat)
282     {
283         var type;
284         if (forceObjectFormat)
285             type = "object";
286         else if (output instanceof WebInspector.RemoteObject)
287             type = output.subtype || output.type;
288         else
289             type = typeof output;
290
291         var formatter = this._customFormatters[type];
292         if (!formatter)
293             formatter = this._formatParameterAsValue;
294
295         var span = document.createElement("span");
296
297         if (this._isExpandable(output))
298             span.classList.add("expandable");
299
300         formatter.call(this, output, span, forceObjectFormat);
301         return span;
302     },
303
304     _formatParameterAsValue: function(value, elem)
305     {
306         elem.appendChild(WebInspector.FormattedValue.createElementForRemoteObject(value));
307     },
308
309     _formatParameterAsObject: function(obj, elem, forceExpansion)
310     {
311         this._objectTree = new WebInspector.ObjectTreeView(obj, WebInspector.ObjectTreeView.Mode.Properties, this._rootPropertyPathForObject(obj), forceExpansion);
312         elem.appendChild(this._objectTree.element);
313     },
314
315     _formatParameterAsString: function(output, elem)
316     {
317         var span = WebInspector.FormattedValue.createLinkifiedElementString(output.description);
318         elem.appendChild(span);
319     },
320
321     _formatParameterAsNode: function(object, elem)
322     {
323         var span = WebInspector.FormattedValue.createElementForNode(object);
324         elem.appendChild(span);
325     },
326
327     _formatParameterAsArray: function(arr, elem)
328     {
329         this._objectTree = new WebInspector.ObjectTreeView(arr, WebInspector.ObjectTreeView.Mode.Properties, this._rootPropertyPathForObject(arr));
330         elem.appendChild(this._objectTree.element);
331     },
332
333     _rootPropertyPathForObject: function(object)
334     {
335         if (!this.savedResultIndex)
336             return null;
337
338         return new WebInspector.PropertyPath(object, "$" + this.savedResultIndex);
339     },
340
341     _userProvidedColumnNames: function(columnNamesArgument)
342     {
343         if (!columnNamesArgument)
344             return null;
345
346         var remoteObject = WebInspector.RemoteObject.fromPayload(columnNamesArgument);
347
348         // Single primitive argument.
349         if (remoteObject.type === "string" || remoteObject.type === "number")
350             return [String(columnNamesArgument.value)];
351
352         // Ignore everything that is not an array with property previews.
353         if (remoteObject.type !== "object" || remoteObject.subtype !== "array" || !remoteObject.preview || !remoteObject.preview.propertyPreviews)
354             return null;
355
356         // Array. Look into the preview and get string values.
357         var extractedColumnNames = [];
358         for (var propertyPreview of remoteObject.preview.propertyPreviews) {
359             if (propertyPreview.type === "string" || propertyPreview.type === "number")
360                 extractedColumnNames.push(String(propertyPreview.value));
361         }
362
363         return extractedColumnNames.length ? extractedColumnNames : null;
364     },
365
366     _formatParameterAsTable: function(parameters)
367     {
368         var element = document.createElement("span");
369         var table = parameters[0];
370         if (!table || !table.preview)
371             return element;
372
373         var rows = [];
374         var columnNames = [];
375         var flatValues = [];
376         var preview = table.preview;
377         var userProvidedColumnNames = false;
378
379         // User provided columnNames.
380         var extractedColumnNames = this._userProvidedColumnNames(parameters[1]);
381         if (extractedColumnNames) {
382             userProvidedColumnNames = true;
383             columnNames = extractedColumnNames;
384         }
385
386         // Check first for valuePreviews in the properties meaning this was an array of objects.
387         if (preview.propertyPreviews) {
388             for (var i = 0; i < preview.propertyPreviews.length; ++i) {
389                 var rowProperty = preview.propertyPreviews[i];
390                 var rowPreview = rowProperty.valuePreview;
391                 if (!rowPreview)
392                     continue;
393
394                 var rowValue = {};
395                 const maxColumnsToRender = 10;
396                 for (var j = 0; j < rowPreview.propertyPreviews.length; ++j) {
397                     var cellProperty = rowPreview.propertyPreviews[j];
398                     var columnRendered = columnNames.contains(cellProperty.name);
399                     if (!columnRendered) {
400                         if (userProvidedColumnNames || columnNames.length === maxColumnsToRender)
401                             continue;
402                         columnRendered = true;
403                         columnNames.push(cellProperty.name);
404                     }
405
406                     rowValue[cellProperty.name] = WebInspector.FormattedValue.createElementForPropertyPreview(cellProperty);
407                 }
408                 rows.push([rowProperty.name, rowValue]);
409             }
410         }
411
412         // If there were valuePreviews, convert to a flat list.
413         if (rows.length) {
414             const emDash = "\u2014";
415             columnNames.unshift(WebInspector.UIString("(Index)"));
416             for (var i = 0; i < rows.length; ++i) {
417                 var rowName = rows[i][0];
418                 var rowValue = rows[i][1];
419                 flatValues.push(rowName);
420                 for (var j = 1; j < columnNames.length; ++j) {
421                     var columnName = columnNames[j];
422                     if (!(columnName in rowValue))
423                         flatValues.push(emDash);
424                     else
425                         flatValues.push(rowValue[columnName]);
426                 }
427             }
428         }
429
430         // If there were no value Previews, then check for an array of values.
431         if (!flatValues.length && preview.propertyPreviews) {
432             for (var i = 0; i < preview.propertyPreviews.length; ++i) {
433                 var rowProperty = preview.propertyPreviews[i];
434                 if (!("value" in rowProperty))
435                     continue;
436
437                 if (!columnNames.length) {
438                     columnNames.push(WebInspector.UIString("Index"));
439                     columnNames.push(WebInspector.UIString("Value"));
440                 }
441
442                 flatValues.push(rowProperty.name);
443                 flatValues.push(WebInspector.FormattedValue.createElementForPropertyPreview(rowProperty));
444             }
445         }
446
447         // If lossless or not table data, output the object so full data can be gotten.
448         if (!preview.lossless || !flatValues.length) {
449             element.appendChild(this._formatParameter(table));
450             if (!flatValues.length)
451                 return element;
452         }
453
454         var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, flatValues);
455         dataGrid.element.classList.add("inline");
456         element.appendChild(dataGrid.element);
457
458         return element;
459     },
460
461     _formatWithSubstitutionString: function(parameters, formattedResult)
462     {
463         var formatters = {};
464
465         function parameterFormatter(force, obj)
466         {
467             return this._formatParameter(obj, force);
468         }
469
470         function stringFormatter(obj)
471         {
472             return obj.description;
473         }
474
475         function floatFormatter(obj)
476         {
477             if (typeof obj.value !== "number")
478                 return parseFloat(obj.description);
479             return obj.value;
480         }
481
482         function integerFormatter(obj)
483         {
484             if (typeof obj.value !== "number")
485                 return parseInt(obj.description);
486             return Math.floor(obj.value);
487         }
488
489         var currentStyle = null;
490         function styleFormatter(obj)
491         {
492             currentStyle = {};
493             var buffer = document.createElement("span");
494             buffer.setAttribute("style", obj.description);
495             for (var i = 0; i < buffer.style.length; i++) {
496                 var property = buffer.style[i];
497                 if (isWhitelistedProperty(property))
498                     currentStyle[property] = buffer.style[property];
499             }
500         }
501
502         function isWhitelistedProperty(property)
503         {
504             var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
505             for (var i = 0; i < prefixes.length; i++) {
506                 if (property.startsWith(prefixes[i]))
507                     return true;
508             }
509             return false;
510         }
511
512         // Firebug uses %o for formatting objects.
513         formatters.o = parameterFormatter.bind(this, false);
514         formatters.s = stringFormatter;
515         formatters.f = floatFormatter;
516
517         // Firebug allows both %i and %d for formatting integers.
518         formatters.i = integerFormatter;
519         formatters.d = integerFormatter;
520
521         // Firebug uses %c for styling the message.
522         formatters.c = styleFormatter;
523
524         // Support %O to force object formatting, instead of the type-based %o formatting.
525         formatters.O = parameterFormatter.bind(this, true);
526
527         function append(a, b)
528         {
529             if (b instanceof Node)
530                 a.appendChild(b);
531             else if (b) {
532                 var toAppend = WebInspector.linkifyStringAsFragment(b.toString());
533                 if (currentStyle) {
534                     var wrapper = document.createElement("span");
535                     for (var key in currentStyle)
536                         wrapper.style[key] = currentStyle[key];
537                     wrapper.appendChild(toAppend);
538                     toAppend = wrapper;
539                 }
540                 var span = document.createElement("span");
541                 span.className = "type-string";
542                 span.appendChild(toAppend);
543                 a.appendChild(span);
544             }
545             return a;
546         }
547
548         // String.format does treat formattedResult like a Builder, result is an object.
549         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
550     },
551
552     decorateMessageElement: function(element)
553     {
554         if (this._element)
555             return this._element;
556
557         element.message = this;
558         element.classList.add("console-message");
559
560         this._element = element;
561
562         switch (this.level) {
563             case WebInspector.LegacyConsoleMessage.MessageLevel.Tip:
564                 element.classList.add("console-tip-level");
565                 element.setAttribute("data-labelprefix", WebInspector.UIString("Tip: "));
566                 break;
567             case WebInspector.LegacyConsoleMessage.MessageLevel.Log:
568                 element.classList.add("console-log-level");
569                 element.setAttribute("data-labelprefix", WebInspector.UIString("Log: "));
570                 break;
571             case WebInspector.LegacyConsoleMessage.MessageLevel.Debug:
572                 element.classList.add("console-debug-level");
573                 element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: "));
574                 break;
575             case WebInspector.LegacyConsoleMessage.MessageLevel.Warning:
576                 element.classList.add("console-warning-level");
577                 element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: "));
578                 break;
579             case WebInspector.LegacyConsoleMessage.MessageLevel.Error:
580                 element.classList.add("console-error-level");
581                 element.setAttribute("data-labelprefix", WebInspector.UIString("Error: "));
582                 break;
583         }
584
585         if (this.type === WebInspector.LegacyConsoleMessage.MessageType.StartGroup || this.type === WebInspector.LegacyConsoleMessage.MessageType.StartGroupCollapsed)
586             element.classList.add("console-group-title");
587
588         element.appendChild(this.formattedMessage);
589
590         if (this.repeatCount > 1)
591             this.updateRepeatCount();
592
593         return element;
594     },
595
596     toMessageElement: function()
597     {
598         if (this._element)
599             return this._element;
600
601         var element = document.createElement("div");
602
603         return this.decorateMessageElement(element);
604     },
605
606     _populateStackTraceTreeElement: function(parentTreeElement)
607     {
608         for (var i = 0; i < this._stackTrace.length; i++) {
609             var frame = this._stackTrace[i];
610
611             var content = document.createElement("div");
612             var messageTextElement = document.createElement("span");
613             messageTextElement.className = "console-message-text";
614             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
615             messageTextElement.appendChild(document.createTextNode(functionName));
616             content.appendChild(messageTextElement);
617
618             if (frame.url && !this._shouldHideURL(frame.url)) {
619                 var urlElement = this._linkifyCallFrame(frame);
620                 content.appendChild(urlElement);
621             }
622
623             var treeElement = new WebInspector.TreeElement(content);
624             parentTreeElement.appendChild(treeElement);
625         }
626     },
627
628     updateRepeatCount: function() {
629         if (!this.repeatCountElement) {
630             this.repeatCountElement = document.createElement("span");
631             this.repeatCountElement.className = "bubble";
632
633             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
634         }
635         this.repeatCountElement.textContent = this.repeatCount;
636     },
637
638     toString: function()
639     {
640         var sourceString;
641         switch (this.source) {
642             case WebInspector.LegacyConsoleMessage.MessageSource.HTML:
643                 sourceString = "HTML";
644                 break;
645             case WebInspector.LegacyConsoleMessage.MessageSource.XML:
646                 sourceString = "XML";
647                 break;
648             case WebInspector.LegacyConsoleMessage.MessageSource.JS:
649                 sourceString = "JS";
650                 break;
651             case WebInspector.LegacyConsoleMessage.MessageSource.Network:
652                 sourceString = "Network";
653                 break;
654             case WebInspector.LegacyConsoleMessage.MessageSource.ConsoleAPI:
655                 sourceString = "ConsoleAPI";
656                 break;
657             case WebInspector.LegacyConsoleMessage.MessageSource.Other:
658                 sourceString = "Other";
659                 break;
660         }
661
662         var typeString;
663         switch (this.type) {
664             case WebInspector.LegacyConsoleMessage.MessageType.Log:
665                 typeString = "Log";
666                 break;
667             case WebInspector.LegacyConsoleMessage.MessageType.Dir:
668                 typeString = "Dir";
669                 break;
670             case WebInspector.LegacyConsoleMessage.MessageType.DirXML:
671                 typeString = "Dir XML";
672                 break;
673             case WebInspector.LegacyConsoleMessage.MessageType.Trace:
674                 typeString = "Trace";
675                 break;
676             case WebInspector.LegacyConsoleMessage.MessageType.StartGroupCollapsed:
677             case WebInspector.LegacyConsoleMessage.MessageType.StartGroup:
678                 typeString = "Start Group";
679                 break;
680             case WebInspector.LegacyConsoleMessage.MessageType.EndGroup:
681                 typeString = "End Group";
682                 break;
683             case WebInspector.LegacyConsoleMessage.MessageType.Assert:
684                 typeString = "Assert";
685                 break;
686             case WebInspector.LegacyConsoleMessage.MessageType.Result:
687                 typeString = "Result";
688                 break;
689         }
690
691         return sourceString + " " + typeString + " " + this.levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
692     },
693
694     get text()
695     {
696         return this._messageText;
697     },
698
699     isEqual: function(msg)
700     {
701         if (!msg)
702             return false;
703
704         if (this._stackTrace) {
705             if (!msg._stackTrace)
706                 return false;
707             var l = this._stackTrace;
708             var r = msg._stackTrace;
709             for (var i = 0; i < l.length; i++) {
710                 if (l[i].url !== r[i].url ||
711                     l[i].functionName !== r[i].functionName ||
712                     l[i].lineNumber !== r[i].lineNumber ||
713                     l[i].columnNumber !== r[i].columnNumber)
714                     return false;
715             }
716         }
717
718         return (this.source === msg.source)
719             && (this.type === msg.type)
720             && (this.level === msg.level)
721             && (this.line === msg.line)
722             && (this.url === msg.url)
723             && (this.message === msg.message)
724             && (this._request === msg._request);
725     },
726
727     get stackTrace()
728     {
729         return this._stackTrace;
730     },
731
732     clone: function()
733     {
734         return WebInspector.LegacyConsoleMessage.create(this.source, this.level, this._messageText, this.type, this.url, this.line, this.column, this.repeatCount, this._parameters, this._stackTrace, this._request);
735     },
736
737     get levelString()
738     {
739         switch (this.level) {
740             case WebInspector.LegacyConsoleMessage.MessageLevel.Tip:
741                 return "Tip";
742             case WebInspector.LegacyConsoleMessage.MessageLevel.Log:
743                 return "Log";
744             case WebInspector.LegacyConsoleMessage.MessageLevel.Warning:
745                 return "Warning";
746             case WebInspector.LegacyConsoleMessage.MessageLevel.Debug:
747                 return "Debug";
748             case WebInspector.LegacyConsoleMessage.MessageLevel.Error:
749                 return "Error";
750         }
751     },
752
753     get clipboardPrefixString()
754     {
755         return "[" + this.levelString + "] ";
756     },
757
758     toClipboardString: function(isPrefixOptional)
759     {
760         var isTrace = this._shouldDumpStackTrace();
761
762         var clipboardString = "";
763         if (this._formattedMessage && !isTrace)
764             clipboardString = this._formattedMessage.querySelector("span").innerText;
765         else
766             clipboardString = this.type === WebInspector.LegacyConsoleMessage.MessageType.Trace ? "console.trace()" : this._message || this._messageText;
767
768         if (!isPrefixOptional || this.enforcesClipboardPrefixString)
769             clipboardString = this.clipboardPrefixString + clipboardString;
770
771         if (isTrace) {
772             this._stackTrace.forEach(function(frame) {
773                 clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)"));
774                 if (frame.url)
775                     clipboardString += " (" + WebInspector.displayNameForURL(frame.url) + ", line " + frame.lineNumber + ")";
776             });
777         } else {
778             var repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : "";
779
780             var urlLine = "";
781             if (this.url) {
782                 var components = [WebInspector.displayNameForURL(this.url), "line " + this.line];
783                 if (repeatString)
784                     components.push(repeatString);
785                 urlLine = " (" + components.join(", ") + ")";
786             } else if (repeatString)
787                 urlLine = " (" + repeatString + ")";
788
789             if (urlLine) {
790                 var lines = clipboardString.split("\n");
791                 lines[0] += urlLine;
792                 clipboardString = lines.join("\n");
793             }
794         }
795
796         return clipboardString;
797     }
798 };
799
800 WebInspector.LegacyConsoleMessageImpl.prototype.__proto__ = WebInspector.LegacyConsoleMessage.prototype;