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
31WebInspector.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
50WebInspector.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    _formatParameter: function(output, forceObjectFormat)
247    {
248        var type;
249        if (forceObjectFormat)
250            type = "object";
251        else if (output instanceof WebInspector.RemoteObject)
252            type = output.subtype || output.type;
253        else
254            type = typeof output;
255
256        var formatter = this._customFormatters[type];
257        if (!formatter) {
258            formatter = this._formatParameterAsValue;
259            output = output.description;
260        }
261
262        var span = document.createElement("span");
263        span.className = "console-formatted-" + type + " source-code";
264        formatter.call(this, output, span);
265        return span;
266    },
267
268    _formatParameterAsValue: function(val, elem)
269    {
270        elem.appendChild(document.createTextNode(val));
271    },
272
273    _formatParameterAsObject: function(obj, elem)
274    {
275        elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description).element);
276    },
277
278    _formatParameterAsNode: function(object, elem)
279    {
280        function printNode(nodeId)
281        {
282            if (!nodeId) {
283                // Sometimes DOM is loaded after the sync message is being formatted, so we get no
284                // nodeId here. So we fall back to object formatting here.
285                this._formatParameterAsObject(object, elem);
286                return;
287            }
288            var treeOutline = new WebInspector.DOMTreeOutline(false, false, true);
289            treeOutline.setVisible(true);
290            treeOutline.rootDOMNode = WebInspector.domTreeManager.nodeForId(nodeId);
291            treeOutline.element.classList.add("outline-disclosure");
292            if (!treeOutline.children[0].hasChildren)
293                treeOutline.element.classList.add("single-node");
294            elem.appendChild(treeOutline.element);
295        }
296        object.pushNodeToFrontend(printNode.bind(this));
297    },
298
299    _formatParameterAsArray: function(arr, elem)
300    {
301        arr.getOwnProperties(this._printArray.bind(this, arr, elem));
302    },
303
304    _formatParameterAsString: function(output, elem)
305    {
306        var span = document.createElement("span");
307        span.className = "console-formatted-string source-code";
308        span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
309
310        // Make black quotes.
311        elem.classList.remove("console-formatted-string");
312        elem.appendChild(document.createTextNode("\""));
313        elem.appendChild(span);
314        elem.appendChild(document.createTextNode("\""));
315    },
316
317    _printArray: function(array, elem, properties)
318    {
319        if (!properties)
320            return;
321
322        var elements = [];
323        for (var i = 0; i < properties.length; ++i) {
324            var property = properties[i];
325            var name = property.name;
326            if (!isNaN(name))
327                elements[name] = this._formatAsArrayEntry(property.value);
328        }
329
330        elem.appendChild(document.createTextNode("["));
331        var lastNonEmptyIndex = -1;
332
333        function appendUndefined(elem, index)
334        {
335            if (index - lastNonEmptyIndex <= 1)
336                return;
337            var span = elem.createChild("span", "console-formatted-undefined");
338            span.textContent = WebInspector.UIString("undefined × %d").format(index - lastNonEmptyIndex - 1);
339        }
340
341        var length = array.arrayLength();
342        for (var i = 0; i < length; ++i) {
343            var element = elements[i];
344            if (!element)
345                continue;
346
347            if (i - lastNonEmptyIndex > 1) {
348                appendUndefined(elem, i);
349                elem.appendChild(document.createTextNode(", "));
350            }
351
352            elem.appendChild(element);
353            lastNonEmptyIndex = i;
354            if (i < length - 1)
355                elem.appendChild(document.createTextNode(", "));
356        }
357        appendUndefined(elem, length);
358
359        elem.appendChild(document.createTextNode("]"));
360    },
361
362    _formatAsArrayEntry: function(output)
363    {
364        // Prevent infinite expansion of cross-referencing arrays.
365        return this._formatParameter(output, output.subtype && output.subtype === "array");
366    },
367
368    _formatWithSubstitutionString: function(parameters, formattedResult)
369    {
370        var formatters = {};
371
372        function parameterFormatter(force, obj)
373        {
374            return this._formatParameter(obj, force);
375        }
376
377        function stringFormatter(obj)
378        {
379            return obj.description;
380        }
381
382        function floatFormatter(obj)
383        {
384            if (typeof obj.value !== "number")
385                return parseFloat(obj.description);
386            return obj.value;
387        }
388
389        function integerFormatter(obj)
390        {
391            if (typeof obj.value !== "number")
392                return parseInt(obj.description);
393            return Math.floor(obj.value);
394        }
395
396        var currentStyle = null;
397        function styleFormatter(obj)
398        {
399            currentStyle = {};
400            var buffer = document.createElement("span");
401            buffer.setAttribute("style", obj.description);
402            for (var i = 0; i < buffer.style.length; i++) {
403                var property = buffer.style[i];
404                if (isWhitelistedProperty(property))
405                    currentStyle[property] = buffer.style[property];
406            }
407        }
408
409        function isWhitelistedProperty(property)
410        {
411            var prefixes = ["background", "border", "color", "font", "line", "margin", "padding", "text", "-webkit-background", "-webkit-border", "-webkit-font", "-webkit-margin", "-webkit-padding", "-webkit-text"];
412            for (var i = 0; i < prefixes.length; i++) {
413                if (property.startsWith(prefixes[i]))
414                    return true;
415            }
416            return false;
417        }
418
419        // Firebug uses %o for formatting objects.
420        formatters.o = parameterFormatter.bind(this, false);
421        formatters.s = stringFormatter;
422        formatters.f = floatFormatter;
423
424        // Firebug allows both %i and %d for formatting integers.
425        formatters.i = integerFormatter;
426        formatters.d = integerFormatter;
427
428        // Firebug uses %c for styling the message.
429        formatters.c = styleFormatter;
430
431        // Support %O to force object formatting, instead of the type-based %o formatting.
432        formatters.O = parameterFormatter.bind(this, true);
433
434        function append(a, b)
435        {
436            if (b instanceof Node)
437                a.appendChild(b);
438            else if (b) {
439                var toAppend = WebInspector.linkifyStringAsFragment(b.toString());
440                if (currentStyle) {
441                    var wrapper = document.createElement("span");
442                    for (var key in currentStyle)
443                        wrapper.style[key] = currentStyle[key];
444                    wrapper.appendChild(toAppend);
445                    toAppend = wrapper;
446                }
447                a.appendChild(toAppend);
448            }
449            return a;
450        }
451
452        // String.format does treat formattedResult like a Builder, result is an object.
453        return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
454    },
455
456    clearHighlight: function()
457    {
458        if (!this._formattedMessage)
459            return;
460
461        var highlightedMessage = this._formattedMessage;
462        delete this._formattedMessage;
463        this._formatMessage();
464        this._element.replaceChild(this._formattedMessage, highlightedMessage);
465    },
466
467    highlightSearchResults: function(regexObject)
468    {
469        if (!this._formattedMessage)
470            return;
471
472        regexObject.lastIndex = 0;
473        var text = this.message;
474        var match = regexObject.exec(text);
475        var offset = 0;
476        var matchRanges = [];
477        while (match) {
478            matchRanges.push({ offset: match.index, length: match[0].length });
479            match = regexObject.exec(text);
480        }
481        highlightSearchResults(this._formattedMessage, matchRanges);
482        this._element.scrollIntoViewIfNeeded();
483    },
484
485    matchesRegex: function(regexObject)
486    {
487        return regexObject.test(this.message);
488    },
489
490    toMessageElement: function()
491    {
492        if (this._element)
493            return this._element;
494
495        var element = document.createElement("div");
496        element.message = this;
497        element.className = "console-message";
498
499        this._element = element;
500
501        switch (this.level) {
502            case WebInspector.ConsoleMessage.MessageLevel.Tip:
503                element.classList.add("console-tip-level");
504                element.setAttribute("data-labelprefix", WebInspector.UIString("Tip: "));
505                break;
506            case WebInspector.ConsoleMessage.MessageLevel.Log:
507                element.classList.add("console-log-level");
508                element.setAttribute("data-labelprefix", WebInspector.UIString("Log: "));
509                break;
510            case WebInspector.ConsoleMessage.MessageLevel.Debug:
511                element.classList.add("console-debug-level");
512                element.setAttribute("data-labelprefix", WebInspector.UIString("Debug: "));
513                break;
514            case WebInspector.ConsoleMessage.MessageLevel.Warning:
515                element.classList.add("console-warning-level");
516                element.setAttribute("data-labelprefix", WebInspector.UIString("Warning: "));
517                break;
518            case WebInspector.ConsoleMessage.MessageLevel.Error:
519                element.classList.add("console-error-level");
520                element.setAttribute("data-labelprefix", WebInspector.UIString("Error: "));
521                break;
522        }
523
524        if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
525            element.classList.add("console-group-title");
526
527        element.appendChild(this.formattedMessage);
528
529        if (this.repeatCount > 1)
530            this.updateRepeatCount();
531
532        return element;
533    },
534
535    _populateStackTraceTreeElement: function(parentTreeElement)
536    {
537        for (var i = 0; i < this._stackTrace.length; i++) {
538            var frame = this._stackTrace[i];
539
540            var content = document.createElement("div");
541            var messageTextElement = document.createElement("span");
542            messageTextElement.className = "console-message-text source-code";
543            var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
544            messageTextElement.appendChild(document.createTextNode(functionName));
545            content.appendChild(messageTextElement);
546
547            if (frame.url && !this._shouldHideURL(frame.url)) {
548                var urlElement = this._linkifyCallFrame(frame);
549                content.appendChild(urlElement);
550            }
551
552            var treeElement = new TreeElement(content);
553            parentTreeElement.appendChild(treeElement);
554        }
555    },
556
557    updateRepeatCount: function() {
558        if (!this.repeatCountElement) {
559            this.repeatCountElement = document.createElement("span");
560            this.repeatCountElement.className = "bubble";
561
562            this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
563        }
564        this.repeatCountElement.textContent = this.repeatCount;
565    },
566
567    toString: function()
568    {
569        var sourceString;
570        switch (this.source) {
571            case WebInspector.ConsoleMessage.MessageSource.HTML:
572                sourceString = "HTML";
573                break;
574            case WebInspector.ConsoleMessage.MessageSource.XML:
575                sourceString = "XML";
576                break;
577            case WebInspector.ConsoleMessage.MessageSource.JS:
578                sourceString = "JS";
579                break;
580            case WebInspector.ConsoleMessage.MessageSource.Network:
581                sourceString = "Network";
582                break;
583            case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI:
584                sourceString = "ConsoleAPI";
585                break;
586            case WebInspector.ConsoleMessage.MessageSource.Other:
587                sourceString = "Other";
588                break;
589        }
590
591        var typeString;
592        switch (this.type) {
593            case WebInspector.ConsoleMessage.MessageType.Log:
594                typeString = "Log";
595                break;
596            case WebInspector.ConsoleMessage.MessageType.Dir:
597                typeString = "Dir";
598                break;
599            case WebInspector.ConsoleMessage.MessageType.DirXML:
600                typeString = "Dir XML";
601                break;
602            case WebInspector.ConsoleMessage.MessageType.Trace:
603                typeString = "Trace";
604                break;
605            case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
606            case WebInspector.ConsoleMessage.MessageType.StartGroup:
607                typeString = "Start Group";
608                break;
609            case WebInspector.ConsoleMessage.MessageType.EndGroup:
610                typeString = "End Group";
611                break;
612            case WebInspector.ConsoleMessage.MessageType.Assert:
613                typeString = "Assert";
614                break;
615            case WebInspector.ConsoleMessage.MessageType.Result:
616                typeString = "Result";
617                break;
618        }
619
620        return sourceString + " " + typeString + " " + this.levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
621    },
622
623    get text()
624    {
625        return this._messageText;
626    },
627
628    isEqual: function(msg)
629    {
630        if (!msg)
631            return false;
632
633        if (this._stackTrace) {
634            if (!msg._stackTrace)
635                return false;
636            var l = this._stackTrace;
637            var r = msg._stackTrace;
638            for (var i = 0; i < l.length; i++) {
639                if (l[i].url !== r[i].url ||
640                    l[i].functionName !== r[i].functionName ||
641                    l[i].lineNumber !== r[i].lineNumber ||
642                    l[i].columnNumber !== r[i].columnNumber)
643                    return false;
644            }
645        }
646
647        return (this.source === msg.source)
648            && (this.type === msg.type)
649            && (this.level === msg.level)
650            && (this.line === msg.line)
651            && (this.url === msg.url)
652            && (this.message === msg.message)
653            && (this._request === msg._request);
654    },
655
656    get stackTrace()
657    {
658        return this._stackTrace;
659    },
660
661    clone: function()
662    {
663        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);
664    },
665
666    get levelString()
667    {
668        switch (this.level) {
669            case WebInspector.ConsoleMessage.MessageLevel.Tip:
670                return "Tip";
671            case WebInspector.ConsoleMessage.MessageLevel.Log:
672                return "Log";
673            case WebInspector.ConsoleMessage.MessageLevel.Warning:
674                return "Warning";
675            case WebInspector.ConsoleMessage.MessageLevel.Debug:
676                return "Debug";
677            case WebInspector.ConsoleMessage.MessageLevel.Error:
678                return "Error";
679        }
680    },
681
682    get clipboardPrefixString()
683    {
684        return "[" + this.levelString + "] ";
685    },
686
687    toClipboardString: function(isPrefixOptional)
688    {
689        var isTrace = this._shouldDumpStackTrace();
690
691        var clipboardString = "";
692        if (this._formattedMessage && !isTrace)
693            clipboardString = this._formattedMessage.querySelector("span").innerText;
694        else
695            clipboardString = this.type === WebInspector.ConsoleMessage.MessageType.Trace ? "console.trace()" : this._message || this._messageText;
696
697        if (!isPrefixOptional || this.enforcesClipboardPrefixString)
698            clipboardString = this.clipboardPrefixString + clipboardString;
699
700        if (isTrace) {
701            this._stackTrace.forEach(function(frame) {
702                clipboardString += "\n\t" + (frame.functionName || WebInspector.UIString("(anonymous function)"));
703                if (frame.url)
704                    clipboardString += " (" + WebInspector.displayNameForURL(frame.url) + ", line " + frame.lineNumber + ")";
705            });
706        } else {
707            var repeatString = this.repeatCount > 1 ? "x" + this.repeatCount : "";
708
709            var urlLine = "";
710            if (this.url) {
711                var components = [WebInspector.displayNameForURL(this.url), "line " + this.line];
712                if (repeatString)
713                    components.push(repeatString);
714                urlLine = " (" + components.join(", ") + ")";
715            } else if (repeatString)
716                urlLine = " (" + repeatString + ")";
717
718            if (urlLine) {
719                var lines = clipboardString.split("\n");
720                lines[0] += urlLine;
721                clipboardString = lines.join("\n");
722            }
723        }
724
725        return clipboardString;
726    }
727}
728
729WebInspector.ConsoleMessageImpl.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
730