Web Inspector: JSContext inspection should report exceptions in the console
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / 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 Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 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         "array":  this._formatParameterAsArray,
45         "node":   this._formatParameterAsNode,
46         "string": this._formatParameterAsString
47     };
48 }
49
50 WebInspector.ConsoleMessageImpl.prototype = {
51
52     enforcesClipboardPrefixString: true,
53
54     _formatMessage: function()
55     {
56         this._formattedMessage = document.createElement("span");
57         this._formattedMessage.className = "console-message-text source-code";
58
59         var messageText;
60         if (this.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) {
61             switch (this.type) {
62                 case WebInspector.ConsoleMessage.MessageType.Trace:
63                     messageText = document.createTextNode("console.trace()");
64                     break;
65                 case WebInspector.ConsoleMessage.MessageType.Assert:
66                     var args = [WebInspector.UIString("Assertion failed:")];
67                     if (this._parameters)
68                         args = args.concat(this._parameters);
69                     messageText = this._format(args);
70                     break;
71                 case WebInspector.ConsoleMessage.MessageType.Dir:
72                     var obj = this._parameters ? this._parameters[0] : undefined;
73                     var args = ["%O", obj];
74                     messageText = this._format(args);
75                     break;
76                 default:
77                     var args = this._parameters || [this._messageText];
78                     messageText = this._format(args);
79             }
80         } else if (this.source === WebInspector.ConsoleMessage.MessageSource.Network) {
81             if (this._request) {
82                 this._stackTrace = this._request.stackTrace;
83                 if (this._request.initiator && this._request.initiator.url) {
84                     this.url = this._request.initiator.url;
85                     this.line = this._request.initiator.lineNumber;
86                 }
87                 messageText = document.createElement("span");
88                 if (this.level === WebInspector.ConsoleMessage.MessageLevel.Error) {
89                     messageText.appendChild(document.createTextNode(this._request.requestMethod + " "));
90                     messageText.appendChild(WebInspector.linkifyRequestAsNode(this._request));
91                     if (this._request.failed)
92                         messageText.appendChild(document.createTextNode(" " + this._request.localizedFailDescription));
93                     else
94                         messageText.appendChild(document.createTextNode(" " + this._request.statusCode + " (" + this._request.statusText + ")"));
95                 } else {
96                     var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(this._messageText, WebInspector.linkifyRequestAsNode.bind(null, this._request, ""));
97                     messageText.appendChild(fragment);
98                 }
99             } else {
100                 if (this.url) {
101                     var anchor = WebInspector.linkifyURLAsNode(this.url, this.url, "console-message-url");
102                     this._formattedMessage.appendChild(anchor);
103                 }
104                 messageText = this._format([this._messageText]);
105             }
106         } else {
107             var args = this._parameters || [this._messageText];
108             messageText = this._format(args);
109         }
110
111         if (this.source !== WebInspector.ConsoleMessage.MessageSource.Network || this._request) {
112             var firstNonNativeCallFrame = this._firstNonNativeCallFrame();
113             if (firstNonNativeCallFrame) {
114                 var urlElement = this._linkifyCallFrame(firstNonNativeCallFrame);
115                 this._formattedMessage.appendChild(urlElement);
116             } else if (this.url && !this._shouldHideURL(this.url)) {
117                 var urlElement = this._linkifyLocation(this.url, this.line, this.column);
118                 this._formattedMessage.appendChild(urlElement);
119             }
120         }
121
122         this._formattedMessage.appendChild(messageText);
123
124         if (this._shouldDumpStackTrace()) {
125             var ol = document.createElement("ol");
126             ol.className = "outline-disclosure";
127             var treeOutline = new TreeOutline(ol);
128
129             var content = this._formattedMessage;
130             var root = new TreeElement(content, null, true);
131             content.treeElementForTest = root;
132             treeOutline.appendChild(root);
133             if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
134                 root.expand();
135
136             this._populateStackTraceTreeElement(root);
137             this._formattedMessage = ol;
138         }
139
140         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
141         this._message = messageText.textContent;
142     },
143
144     _shouldDumpStackTrace: function()
145     {
146         return !!this._stackTrace && this._stackTrace.length && (this.source === WebInspector.ConsoleMessage.MessageSource.Network || this.level === WebInspector.ConsoleMessage.MessageLevel.Error || this.type === WebInspector.ConsoleMessage.MessageType.Trace);
147     },
148
149     _shouldHideURL: function(url)
150     {
151         return url === "undefined" || url === "[native code]";
152     },
153
154     _firstNonNativeCallFrame: function()
155     {
156         if (!this._stackTrace)
157             return null;
158
159         for (var i = 0; i < this._stackTrace.length; i++) {
160             var frame = this._stackTrace[i];
161             if (!frame.url || frame.url === "[native code]")
162                 continue;
163             return frame;
164         }
165
166         return null;
167     },
168
169     get message()
170     {
171         // force message formatting
172         var formattedMessage = this.formattedMessage;
173         return this._message;
174     },
175
176     get formattedMessage()
177     {
178         if (!this._formattedMessage)
179             this._formatMessage();
180         return this._formattedMessage;
181     },
182
183     _linkifyLocation: function(url, lineNumber, columnNumber)
184     {
185         // ConsoleMessage stack trace line numbers are one-based.
186         lineNumber = lineNumber ? lineNumber - 1 : 0;
187
188         return WebInspector.linkifyLocation(url, lineNumber, columnNumber, "console-message-url");
189     },
190
191     _linkifyCallFrame: function(callFrame)
192     {
193         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
194     },
195
196     isErrorOrWarning: function()
197     {
198         return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
199     },
200
201     _format: function(parameters)
202     {
203         // This node is used like a Builder. Values are continually appended onto it.
204         var formattedResult = document.createElement("span");
205         if (!parameters.length)
206             return formattedResult;
207
208         // Formatting code below assumes that parameters are all wrappers whereas frontend console
209         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
210         for (var i = 0; i < parameters.length; ++i) {
211             // FIXME: Only pass runtime wrappers here.
212             if (parameters[i] instanceof WebInspector.RemoteObject)
213                 continue;
214
215             if (typeof parameters[i] === "object")
216                 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
217             else
218                 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
219         }
220
221         // There can be string log and string eval result. We distinguish between them based on message type.
222         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
223
224         // Multiple parameters with the first being a format string. Save unused substitutions.
225         if (shouldFormatMessage) {
226             // Multiple parameters with the first being a format string. Save unused substitutions.
227             var result = this._formatWithSubstitutionString(parameters, formattedResult);
228             parameters = result.unusedSubstitutions;
229             if (parameters.length)
230                 formattedResult.appendChild(document.createTextNode(" "));
231         }
232
233         // Single parameter, or unused substitutions from above.
234         for (var i = 0; i < parameters.length; ++i) {
235             // Inline strings when formatting.
236             if (shouldFormatMessage && parameters[i].type === "string")
237                 formattedResult.appendChild(document.createTextNode(parameters[i].description));
238             else
239                 formattedResult.appendChild(this._formatParameter(parameters[i]));
240             if (i < parameters.length - 1)
241                 formattedResult.appendChild(document.createTextNode(" "));
242         }
243         return formattedResult;
244     },
245
246     /**
247      * @param {boolean=} forceObjectFormat
248      */
249     _formatParameter: function(output, forceObjectFormat)
250     {
251         var type;
252         if (forceObjectFormat)
253             type = "object";
254         else if (output instanceof WebInspector.RemoteObject)
255             type = output.subtype || output.type;
256         else
257             type = typeof output;
258
259         var formatter = this._customFormatters[type];
260         if (!formatter) {
261             formatter = this._formatParameterAsValue;
262             output = output.description;
263         }
264
265         var span = document.createElement("span");
266         span.className = "console-formatted-" + type + " source-code";
267         formatter.call(this, output, span);
268         return span;
269     },
270
271     _formatParameterAsValue: function(val, elem)
272     {
273         elem.appendChild(document.createTextNode(val));
274     },
275
276     _formatParameterAsObject: function(obj, elem)
277     {
278         elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description).element);
279     },
280
281     _formatParameterAsNode: function(object, elem)
282     {
283         function printNode(nodeId)
284         {
285             if (!nodeId) {
286                 // Sometimes DOM is loaded after the sync message is being formatted, so we get no
287                 // nodeId here. So we fall back to object formatting here.
288                 this._formatParameterAsObject(object, elem);
289                 return;
290             }
291             var treeOutline = new WebInspector.DOMTreeOutline(false, false, true);
292             treeOutline.setVisible(true);
293             treeOutline.rootDOMNode = WebInspector.domTreeManager.nodeForId(nodeId);
294             treeOutline.element.classList.add("outline-disclosure");
295             if (!treeOutline.children[0].hasChildren)
296                 treeOutline.element.classList.add("single-node");
297             elem.appendChild(treeOutline.element);
298         }
299         object.pushNodeToFrontend(printNode.bind(this));
300     },
301
302     _formatParameterAsArray: function(arr, elem)
303     {
304         arr.getOwnProperties(this._printArray.bind(this, arr, elem));
305     },
306
307     _formatParameterAsString: function(output, elem)
308     {
309         var span = document.createElement("span");
310         span.className = "console-formatted-string source-code";
311         span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
312
313         // Make black quotes.
314         elem.classList.remove("console-formatted-string");
315         elem.appendChild(document.createTextNode("\""));
316         elem.appendChild(span);
317         elem.appendChild(document.createTextNode("\""));
318     },
319
320     _printArray: function(array, elem, properties)
321     {
322         if (!properties)
323             return;
324
325         var elements = [];
326         for (var i = 0; i < properties.length; ++i) {
327             var property = properties[i];
328             var name = property.name;
329             if (!isNaN(name))
330                 elements[name] = this._formatAsArrayEntry(property.value);
331         }
332
333         elem.appendChild(document.createTextNode("["));
334         var lastNonEmptyIndex = -1;
335
336         function appendUndefined(elem, index)
337         {
338             if (index - lastNonEmptyIndex <= 1)
339                 return;
340             var span = elem.createChild("span", "console-formatted-undefined");
341             span.textContent = WebInspector.UIString("undefined × %d").format(index - lastNonEmptyIndex - 1);
342         }
343
344         var length = array.arrayLength();
345         for (var i = 0; i < length; ++i) {
346             var element = elements[i];
347             if (!element)
348                 continue;
349
350             if (i - lastNonEmptyIndex > 1) {
351                 appendUndefined(elem, i);
352                 elem.appendChild(document.createTextNode(", "));
353             }
354
355             elem.appendChild(element);
356             lastNonEmptyIndex = i;
357             if (i < length - 1)
358                 elem.appendChild(document.createTextNode(", "));
359         }
360         appendUndefined(elem, length);
361
362         elem.appendChild(document.createTextNode("]"));
363     },
364
365     _formatAsArrayEntry: function(output)
366     {
367         // Prevent infinite expansion of cross-referencing arrays.
368         return this._formatParameter(output, output.subtype && output.subtype === "array");
369     },
370
371     _formatWithSubstitutionString: function(parameters, formattedResult)
372     {
373         var formatters = {};
374
375         function parameterFormatter(force, obj)
376         {
377             return this._formatParameter(obj, force);
378         }
379
380         function stringFormatter(obj)
381         {
382             return obj.description;
383         }
384
385         function floatFormatter(obj)
386         {
387             if (typeof obj.value !== "number")
388                 return parseFloat(obj.description);
389             return obj.value;
390         }
391
392         function integerFormatter(obj)
393         {
394             if (typeof obj.value !== "number")
395                 return parseInt(obj.description);
396             return Math.floor(obj.value);
397         }
398
399         var currentStyle = null;
400         function styleFormatter(obj)
401         {
402             currentStyle = {};
403             var buffer = document.createElement("span");
404             buffer.setAttribute("style", obj.description);
405             for (var i = 0; i < buffer.style.length; i++) {
406                 var property = buffer.style[i];
407                 if (isWhitelistedProperty(property))
408                     currentStyle[property] = buffer.style[property];
409             }
410         }
411
412         function isWhitelistedProperty(property)
413         {
414             var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
415             for (var i = 0; i < prefixes.length; i++) {
416                 if (property.startsWith(prefixes[i]))
417                     return true;
418             }
419             return false;
420         }
421
422         // Firebug uses %o for formatting objects.
423         formatters.o = parameterFormatter.bind(this, false);
424         formatters.s = stringFormatter;
425         formatters.f = floatFormatter;
426
427         // Firebug allows both %i and %d for formatting integers.
428         formatters.i = integerFormatter;
429         formatters.d = integerFormatter;
430
431         // Firebug uses %c for styling the message.
432         formatters.c = styleFormatter;
433
434         // Support %O to force object formatting, instead of the type-based %o formatting.
435         formatters.O = parameterFormatter.bind(this, true);
436
437         function append(a, b)
438         {
439             if (b instanceof Node)
440                 a.appendChild(b);
441             else if (b) {
442                 var toAppend = WebInspector.linkifyStringAsFragment(b.toString());
443                 if (currentStyle) {
444                     var wrapper = document.createElement("span");
445                     for (var key in currentStyle)
446                         wrapper.style[key] = currentStyle[key];
447                     wrapper.appendChild(toAppend);
448                     toAppend = wrapper;
449                 }
450                 a.appendChild(toAppend);
451             }
452             return a;
453         }
454
455         // String.format does treat formattedResult like a Builder, result is an object.
456         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
457     },
458
459     clearHighlight: function()
460     {
461         if (!this._formattedMessage)
462             return;
463
464         var highlightedMessage = this._formattedMessage;
465         delete this._formattedMessage;
466         this._formatMessage();
467         this._element.replaceChild(this._formattedMessage, highlightedMessage);
468     },
469
470     highlightSearchResults: function(regexObject)
471     {
472         if (!this._formattedMessage)
473             return;
474
475         regexObject.lastIndex = 0;
476         var text = this.message;
477         var match = regexObject.exec(text);
478         var offset = 0;
479         var matchRanges = [];
480         while (match) {
481             matchRanges.push({ offset: match.index, length: match[0].length });
482             match = regexObject.exec(text);
483         }
484         highlightSearchResults(this._formattedMessage, matchRanges);
485         this._element.scrollIntoViewIfNeeded();
486     },
487
488     matchesRegex: function(regexObject)
489     {
490         return regexObject.test(this.message);
491     },
492
493     toMessageElement: function()
494     {
495         if (this._element)
496             return this._element;
497
498         var element = document.createElement("div");
499         element.message = this;
500         element.className = "console-message";
501
502         this._element = element;
503
504         switch (this.level) {
505             case WebInspector.ConsoleMessage.MessageLevel.Tip:
506                 element.classList.add("console-tip-level");
507                 element.setAttribute("data-labelprefix", WebInspector.UIString("Tip: "));
508                 break;
509             case WebInspector.ConsoleMessage.MessageLevel.Log:
510                 element.classList.add("console-log-level");
511                 element.setAttribute("data-labelprefix", WebInspector.UIString("Log: "));
512                 break;
513             case WebInspector.ConsoleMessage.MessageLevel.Debug:
514                 element.classList.add("console-debug-level");
515                 element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: "));
516                 break;
517             case WebInspector.ConsoleMessage.MessageLevel.Warning:
518                 element.classList.add("console-warning-level");
519                 element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: "));
520                 break;
521             case WebInspector.ConsoleMessage.MessageLevel.Error:
522                 element.classList.add("console-error-level");
523                 element.setAttribute("data-labelprefix", WebInspector.UIString("Error: "));
524                 break;
525         }
526
527         if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
528             element.classList.add("console-group-title");
529
530         element.appendChild(this.formattedMessage);
531
532         if (this.repeatCount > 1)
533             this.updateRepeatCount();
534
535         return element;
536     },
537
538     _populateStackTraceTreeElement: function(parentTreeElement)
539     {
540         for (var i = 0; i < this._stackTrace.length; i++) {
541             var frame = this._stackTrace[i];
542
543             var content = document.createElement("div");
544             var messageTextElement = document.createElement("span");
545             messageTextElement.className = "console-message-text source-code";
546             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
547             messageTextElement.appendChild(document.createTextNode(functionName));
548             content.appendChild(messageTextElement);
549
550             if (frame.url && !this._shouldHideURL(frame.url)) {
551                 var urlElement = this._linkifyCallFrame(frame);
552                 content.appendChild(urlElement);
553             }
554
555             var treeElement = new TreeElement(content);
556             parentTreeElement.appendChild(treeElement);
557         }
558     },
559
560     updateRepeatCount: function() {
561         if (!this.repeatCountElement) {
562             this.repeatCountElement = document.createElement("span");
563             this.repeatCountElement.className = "bubble";
564
565             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
566         }
567         this.repeatCountElement.textContent = this.repeatCount;
568     },
569
570     toString: function()
571     {
572         var sourceString;
573         switch (this.source) {
574             case WebInspector.ConsoleMessage.MessageSource.HTML:
575                 sourceString = "HTML";
576                 break;
577             case WebInspector.ConsoleMessage.MessageSource.XML:
578                 sourceString = "XML";
579                 break;
580             case WebInspector.ConsoleMessage.MessageSource.JS:
581                 sourceString = "JS";
582                 break;
583             case WebInspector.ConsoleMessage.MessageSource.Network:
584                 sourceString = "Network";
585                 break;
586             case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
587                 sourceString = "ConsoleAPI";
588                 break;
589             case WebInspector.ConsoleMessage.MessageSource.Other:
590                 sourceString = "Other";
591                 break;
592         }
593
594         var typeString;
595         switch (this.type) {
596             case WebInspector.ConsoleMessage.MessageType.Log:
597                 typeString = "Log";
598                 break;
599             case WebInspector.ConsoleMessage.MessageType.Dir:
600                 typeString = "Dir";
601                 break;
602             case WebInspector.ConsoleMessage.MessageType.DirXML:
603                 typeString = "Dir XML";
604                 break;
605             case WebInspector.ConsoleMessage.MessageType.Trace:
606                 typeString = "Trace";
607                 break;
608             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
609             case WebInspector.ConsoleMessage.MessageType.StartGroup:
610                 typeString = "Start Group";
611                 break;
612             case WebInspector.ConsoleMessage.MessageType.EndGroup:
613                 typeString = "End Group";
614                 break;
615             case WebInspector.ConsoleMessage.MessageType.Assert:
616                 typeString = "Assert";
617                 break;
618             case WebInspector.ConsoleMessage.MessageType.Result:
619                 typeString = "Result";
620                 break;
621         }
622
623         return sourceString + " " + typeString + " " + this.levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
624     },
625
626     get text()
627     {
628         return this._messageText;
629     },
630
631     isEqual: function(msg)
632     {
633         if (!msg)
634             return false;
635
636         if (this._stackTrace) {
637             if (!msg._stackTrace)
638                 return false;
639             var l = this._stackTrace;
640             var r = msg._stackTrace;
641             for (var i = 0; i < l.length; i++) {
642                 if (l[i].url !== r[i].url ||
643                     l[i].functionName !== r[i].functionName ||
644                     l[i].lineNumber !== r[i].lineNumber ||
645                     l[i].columnNumber !== r[i].columnNumber)
646                     return false;
647             }
648         }
649
650         return (this.source === msg.source)
651             && (this.type === msg.type)
652             && (this.level === msg.level)
653             && (this.line === msg.line)
654             && (this.url === msg.url)
655             && (this.message === msg.message)
656             && (this._request === msg._request);
657     },
658
659     get stackTrace()
660     {
661         return this._stackTrace;
662     },
663
664     /**
665      * @return {WebInspector.ConsoleMessage}
666      */
667     clone: function()
668     {
669         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);
670     },
671
672     get levelString()
673     {
674         switch (this.level) {
675             case WebInspector.ConsoleMessage.MessageLevel.Tip:
676                 return "Tip";
677             case WebInspector.ConsoleMessage.MessageLevel.Log:
678                 return "Log";
679             case WebInspector.ConsoleMessage.MessageLevel.Warning:
680                 return "Warning";
681             case WebInspector.ConsoleMessage.MessageLevel.Debug:
682                 return "Debug";
683             case WebInspector.ConsoleMessage.MessageLevel.Error:
684                 return "Error";
685         }
686     },
687
688     get clipboardPrefixString()
689     {
690         return "[" + this.levelString + "] ";
691     },
692
693     toClipboardString: function(isPrefixOptional)
694     {
695         var isTrace = this._shouldDumpStackTrace();
696         
697         var clipboardString = "";
698         if (this._formattedMessage && !isTrace)
699             clipboardString = this._formattedMessage.querySelector("span").innerText;
700         else
701             clipboardString = this.type === WebInspector.ConsoleMessage.MessageType.Trace ? "console.trace()" : this._message || this._messageText;
702
703         if (!isPrefixOptional || this.enforcesClipboardPrefixString)
704             clipboardString = this.clipboardPrefixString + clipboardString;
705
706         if (isTrace) {
707             this._stackTrace.forEach(function(frame) {
708                 clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)"));
709                 if (frame.url)
710                     clipboardString += " (" + WebInspector.displayNameForURL(frame.url) + ", line " + frame.lineNumber + ")";
711             });
712         } else {
713             var repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : "";
714
715             var urlLine = "";
716             if (this.url) {
717                 var components = [WebInspector.displayNameForURL(this.url), "line " + this.line];
718                 if (repeatString)
719                     components.push(repeatString);
720                 urlLine = " (" + components.join(", ") + ")";
721             } else if (repeatString)
722                 urlLine = " (" + repeatString + ")";
723         
724             if (urlLine) {
725                 var lines = clipboardString.split("\n");
726                 lines[0] += urlLine;
727                 clipboardString = lines.join("\n");
728             }
729         }
730
731         return clipboardString;
732     }
733 }
734
735 WebInspector.ConsoleMessageImpl.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;