1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.SDKModel}
34 * @param {!WebInspector.Target} target
35 */
36WebInspector.ConsoleModel = function(target)
37{
38    WebInspector.SDKModel.call(this, WebInspector.ConsoleModel, target);
39
40    /** @type {!Array.<!WebInspector.ConsoleMessage>} */
41    this._messages = [];
42    this.warnings = 0;
43    this.errors = 0;
44    this._consoleAgent = target.consoleAgent();
45    target.registerConsoleDispatcher(new WebInspector.ConsoleDispatcher(this));
46    this._enableAgent();
47}
48
49WebInspector.ConsoleModel.Events = {
50    ConsoleCleared: "ConsoleCleared",
51    MessageAdded: "MessageAdded",
52    CommandEvaluated: "CommandEvaluated",
53}
54
55WebInspector.ConsoleModel.prototype = {
56    _enableAgent: function()
57    {
58        if (WebInspector.settings.monitoringXHREnabled.get())
59            this._consoleAgent.setMonitoringXHREnabled(true);
60
61        this._enablingConsole = true;
62
63        /**
64         * @this {WebInspector.ConsoleModel}
65         */
66        function callback()
67        {
68            delete this._enablingConsole;
69        }
70        this._consoleAgent.enable(callback.bind(this));
71    },
72
73    /**
74     * @return {boolean}
75     */
76    enablingConsole: function()
77    {
78        return !!this._enablingConsole;
79    },
80
81    /**
82     * @param {!WebInspector.ConsoleMessage} msg
83     */
84    addMessage: function(msg)
85    {
86        if (WebInspector.NetworkManager.hasDevToolsRequestHeader(msg.request))
87            return;
88
89        msg.index = this._messages.length;
90        this._messages.push(msg);
91        this._incrementErrorWarningCount(msg);
92
93        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.MessageAdded, msg);
94    },
95
96    /**
97     * @param {!WebInspector.ConsoleMessage} msg
98     */
99    _incrementErrorWarningCount: function(msg)
100    {
101        switch (msg.level) {
102            case WebInspector.ConsoleMessage.MessageLevel.Warning:
103                this.warnings++;
104                break;
105            case WebInspector.ConsoleMessage.MessageLevel.Error:
106                this.errors++;
107                break;
108        }
109    },
110
111    /**
112     * @return {!Array.<!WebInspector.ConsoleMessage>}
113     */
114    messages: function()
115    {
116        return this._messages;
117    },
118
119    requestClearMessages: function()
120    {
121        this._consoleAgent.clearMessages();
122        this._messagesCleared();
123    },
124
125    _messagesCleared: function()
126    {
127        this._messages = [];
128        this.errors = 0;
129        this.warnings = 0;
130        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.ConsoleCleared);
131    },
132
133    __proto__: WebInspector.SDKModel.prototype
134}
135
136/**
137 * @param {!WebInspector.ExecutionContext} executionContext
138 * @param {string} text
139 * @param {boolean=} useCommandLineAPI
140 */
141WebInspector.ConsoleModel.evaluateCommandInConsole = function(executionContext, text, useCommandLineAPI)
142{
143    useCommandLineAPI = !!useCommandLineAPI;
144    var target = executionContext.target();
145
146    var commandMessage = new WebInspector.ConsoleMessage(target, WebInspector.ConsoleMessage.MessageSource.JS, null, text, WebInspector.ConsoleMessage.MessageType.Command);
147    commandMessage.setExecutionContextId(executionContext.id);
148    target.consoleModel.addMessage(commandMessage);
149
150    /**
151     * @param {?WebInspector.RemoteObject} result
152     * @param {boolean} wasThrown
153     * @param {?RuntimeAgent.RemoteObject=} valueResult
154     * @param {?DebuggerAgent.ExceptionDetails=} exceptionDetails
155     * @this {WebInspector.ConsoleModel}
156     */
157    function printResult(result, wasThrown, valueResult, exceptionDetails)
158    {
159        if (!result)
160            return;
161
162        WebInspector.console.show();
163        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.CommandEvaluated, {result: result, wasThrown: wasThrown, text: text, commandMessage: commandMessage, exceptionDetails: exceptionDetails});
164    }
165
166    executionContext.evaluate(text, "console", useCommandLineAPI, false, false, true, printResult.bind(target.consoleModel));
167
168    WebInspector.userMetrics.ConsoleEvaluated.record();
169}
170
171
172/**
173 * @constructor
174 * @param {?WebInspector.Target} target
175 * @param {string} source
176 * @param {?string} level
177 * @param {string} messageText
178 * @param {string=} type
179 * @param {?string=} url
180 * @param {number=} line
181 * @param {number=} column
182 * @param {!NetworkAgent.RequestId=} requestId
183 * @param {!Array.<!RuntimeAgent.RemoteObject>=} parameters
184 * @param {!Array.<!ConsoleAgent.CallFrame>=} stackTrace
185 * @param {number=} timestamp
186 * @param {boolean=} isOutdated
187 * @param {!RuntimeAgent.ExecutionContextId=} executionContextId
188 * @param {!ConsoleAgent.AsyncStackTrace=} asyncStackTrace
189 * @param {?string=} scriptId
190 */
191WebInspector.ConsoleMessage = function(target, source, level, messageText, type, url, line, column, requestId, parameters, stackTrace, timestamp, isOutdated, executionContextId, asyncStackTrace, scriptId)
192{
193    this._target = target;
194    this.source = source;
195    this.level = level;
196    this.messageText = messageText;
197    this.type = type || WebInspector.ConsoleMessage.MessageType.Log;
198    /** @type {string|undefined} */
199    this.url = url || undefined;
200    /** @type {number} */
201    this.line = line || 0;
202    /** @type {number} */
203    this.column = column || 0;
204    this.parameters = parameters;
205    /** @type {!Array.<!ConsoleAgent.CallFrame>|undefined} */
206    this.stackTrace = stackTrace;
207    this.timestamp = timestamp || Date.now();
208    this.isOutdated = isOutdated;
209    this.executionContextId = executionContextId || 0;
210    this.asyncStackTrace = asyncStackTrace;
211    this.scriptId = scriptId || null;
212
213    this.request = requestId ? target.networkLog.requestForId(requestId) : null;
214
215    if (this.request) {
216        var initiator = this.request.initiator();
217        if (initiator) {
218            this.stackTrace = initiator.stackTrace || undefined;
219            this.asyncStackTrace = initiator.asyncStackTrace;
220            if (initiator.url) {
221                this.url = initiator.url;
222                this.line = initiator.lineNumber || 0;
223            }
224        }
225    }
226}
227
228WebInspector.ConsoleMessage.prototype = {
229    /**
230     * @return {?WebInspector.Target}
231     */
232    target: function()
233    {
234        return this._target;
235    },
236
237    /**
238     * @param {!WebInspector.ConsoleMessage} originatingMessage
239     */
240    setOriginatingMessage: function(originatingMessage)
241    {
242        this._originatingConsoleMessage = originatingMessage;
243        this.executionContextId = originatingMessage.executionContextId;
244    },
245
246    /**
247     * @param {!RuntimeAgent.ExecutionContextId} executionContextId
248     */
249    setExecutionContextId: function(executionContextId)
250    {
251        this.executionContextId = executionContextId;
252    },
253
254    /**
255     * @return {?WebInspector.ConsoleMessage}
256     */
257    originatingMessage: function()
258    {
259        return this._originatingConsoleMessage;
260    },
261
262    /**
263     * @return {boolean}
264     */
265    isGroupMessage: function()
266    {
267        return this.type === WebInspector.ConsoleMessage.MessageType.StartGroup ||
268            this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed ||
269            this.type === WebInspector.ConsoleMessage.MessageType.EndGroup;
270    },
271
272    /**
273     * @return {boolean}
274     */
275    isGroupStartMessage: function()
276    {
277        return this.type === WebInspector.ConsoleMessage.MessageType.StartGroup ||
278            this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed;
279    },
280
281    /**
282     * @return {boolean}
283     */
284    isErrorOrWarning: function()
285    {
286        return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
287    },
288
289    /**
290     * @return {!WebInspector.ConsoleMessage}
291     */
292    clone: function()
293    {
294        return new WebInspector.ConsoleMessage(
295            this.target(),
296            this.source,
297            this.level,
298            this.messageText,
299            this.type,
300            this.url,
301            this.line,
302            this.column,
303            this.request ? this.request.requestId : undefined,
304            this.parameters,
305            this.stackTrace,
306            this.timestamp,
307            this.isOutdated,
308            this.executionContextId,
309            this.asyncStackTrace,
310            this.scriptId);
311    },
312
313    /**
314     * @param {?WebInspector.ConsoleMessage} msg
315     * @return {boolean}
316     */
317    isEqual: function(msg)
318    {
319        if (!msg)
320            return false;
321
322        if (!this._isEqualStackTraces(this.stackTrace, msg.stackTrace))
323            return false;
324
325        var asyncTrace1 = this.asyncStackTrace;
326        var asyncTrace2 = msg.asyncStackTrace;
327        while (asyncTrace1 || asyncTrace2) {
328            if (!asyncTrace1 || !asyncTrace2)
329                return false;
330            if (asyncTrace1.description !== asyncTrace2.description)
331                return false;
332            if (!this._isEqualStackTraces(asyncTrace1.callFrames, asyncTrace2.callFrames))
333                return false;
334            asyncTrace1 = asyncTrace1.asyncStackTrace;
335            asyncTrace2 = asyncTrace2.asyncStackTrace;
336        }
337
338        if (this.parameters) {
339            if (!msg.parameters || this.parameters.length !== msg.parameters.length)
340                return false;
341
342            for (var i = 0; i < msg.parameters.length; ++i) {
343                // Never treat objects as equal - their properties might change over time.
344                if (this.parameters[i].type !== msg.parameters[i].type || msg.parameters[i].type === "object" || this.parameters[i].value !== msg.parameters[i].value)
345                    return false;
346            }
347        }
348
349        return (this.target() === msg.target())
350            && (this.source === msg.source)
351            && (this.type === msg.type)
352            && (this.level === msg.level)
353            && (this.line === msg.line)
354            && (this.url === msg.url)
355            && (this.messageText === msg.messageText)
356            && (this.request === msg.request)
357            && (this.executionContextId === msg.executionContextId)
358            && (this.scriptId === msg.scriptId);
359    },
360
361    /**
362     * @param {!Array.<!ConsoleAgent.CallFrame>|undefined} stackTrace1
363     * @param {!Array.<!ConsoleAgent.CallFrame>|undefined} stackTrace2
364     * @return {boolean}
365     */
366    _isEqualStackTraces: function(stackTrace1, stackTrace2)
367    {
368        stackTrace1 = stackTrace1 || [];
369        stackTrace2 = stackTrace2 || [];
370        if (stackTrace1.length !== stackTrace2.length)
371            return false;
372        for (var i = 0, n = stackTrace1.length; i < n; ++i) {
373            if (stackTrace1[i].url !== stackTrace2[i].url ||
374                stackTrace1[i].functionName !== stackTrace2[i].functionName ||
375                stackTrace1[i].lineNumber !== stackTrace2[i].lineNumber ||
376                stackTrace1[i].columnNumber !== stackTrace2[i].columnNumber)
377                return false;
378        }
379        return true;
380    }
381}
382
383// Note: Keep these constants in sync with the ones in Console.h
384/**
385 * @enum {string}
386 */
387WebInspector.ConsoleMessage.MessageSource = {
388    XML: "xml",
389    JS: "javascript",
390    Network: "network",
391    ConsoleAPI: "console-api",
392    Storage: "storage",
393    AppCache: "appcache",
394    Rendering: "rendering",
395    CSS: "css",
396    Security: "security",
397    Other: "other",
398    Deprecation: "deprecation"
399}
400
401/**
402 * @enum {string}
403 */
404WebInspector.ConsoleMessage.MessageType = {
405    Log: "log",
406    Dir: "dir",
407    DirXML: "dirxml",
408    Table: "table",
409    Trace: "trace",
410    Clear: "clear",
411    StartGroup: "startGroup",
412    StartGroupCollapsed: "startGroupCollapsed",
413    EndGroup: "endGroup",
414    Assert: "assert",
415    Result: "result",
416    Profile: "profile",
417    ProfileEnd: "profileEnd",
418    Command: "command"
419}
420
421/**
422 * @enum {string}
423 */
424WebInspector.ConsoleMessage.MessageLevel = {
425    Log: "log",
426    Info: "info",
427    Warning: "warning",
428    Error: "error",
429    Debug: "debug"
430};
431
432WebInspector.ConsoleMessage._messageLevelPriority = {
433    "debug": 0,
434    "log": 1,
435    "info": 2,
436    "warning": 3,
437    "error": 4
438};
439
440/**
441 * @param {!WebInspector.ConsoleMessage} a
442 * @param {!WebInspector.ConsoleMessage} b
443 * @return {number}
444 */
445WebInspector.ConsoleMessage.messageLevelComparator = function(a, b)
446{
447    return WebInspector.ConsoleMessage._messageLevelPriority[a.level] - WebInspector.ConsoleMessage._messageLevelPriority[b.level];
448}
449
450/**
451 * @param {!WebInspector.ConsoleMessage} a
452 * @param {!WebInspector.ConsoleMessage} b
453 * @return {number}
454 */
455WebInspector.ConsoleMessage.timestampComparator = function (a, b)
456{
457    return a.timestamp - b.timestamp;
458}
459
460/**
461 * @constructor
462 * @implements {ConsoleAgent.Dispatcher}
463 * @param {!WebInspector.ConsoleModel} console
464 */
465WebInspector.ConsoleDispatcher = function(console)
466{
467    this._console = console;
468}
469
470WebInspector.ConsoleDispatcher.prototype = {
471    /**
472     * @param {!ConsoleAgent.ConsoleMessage} payload
473     */
474    messageAdded: function(payload)
475    {
476        var consoleMessage = new WebInspector.ConsoleMessage(
477            this._console.target(),
478            payload.source,
479            payload.level,
480            payload.text,
481            payload.type,
482            payload.url,
483            payload.line,
484            payload.column,
485            payload.networkRequestId,
486            payload.parameters,
487            payload.stackTrace,
488            payload.timestamp * 1000, // Convert to ms.
489            this._console._enablingConsole,
490            payload.executionContextId,
491            payload.asyncStackTrace,
492            payload.scriptId);
493        this._console.addMessage(consoleMessage);
494    },
495
496    /**
497     * @param {number} count
498     */
499    messageRepeatCountUpdated: function(count)
500    {
501    },
502
503    messagesCleared: function()
504    {
505        if (!WebInspector.settings.preserveConsoleLog.get())
506            this._console._messagesCleared();
507    }
508}
509
510/**
511 * @constructor
512 * @extends {WebInspector.Object}
513 * @implements {WebInspector.TargetManager.Observer}
514 */
515WebInspector.MultitargetConsoleModel = function()
516{
517    WebInspector.targetManager.observeTargets(this);
518    WebInspector.targetManager.addModelListener(WebInspector.ConsoleModel, WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
519    WebInspector.targetManager.addModelListener(WebInspector.ConsoleModel, WebInspector.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this);
520}
521
522WebInspector.MultitargetConsoleModel.prototype = {
523    /**
524     * @param {!WebInspector.Target} target
525     */
526    targetAdded: function(target)
527    {
528        if (!this._mainTarget) {
529            this._mainTarget = target;
530            target.consoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
531        }
532    },
533
534    /**
535     * @param {!WebInspector.Target} target
536     */
537    targetRemoved: function(target)
538    {
539        if (this._mainTarget === target) {
540            delete this._mainTarget;
541            target.consoleModel.removeEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
542        }
543    },
544
545    /**
546     * @return {!Array.<!WebInspector.ConsoleMessage>}
547     */
548    messages: function()
549    {
550        var targets = WebInspector.targetManager.targets();
551        var result = [];
552        for (var i = 0; i < targets.length; ++i)
553            result = result.concat(targets[i].consoleModel.messages());
554        return result;
555    },
556
557    _consoleCleared: function()
558    {
559        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.ConsoleCleared);
560    },
561
562    /**
563     * @param {!WebInspector.Event} event
564     */
565    _consoleMessageAdded: function(event)
566    {
567        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.MessageAdded, event.data);
568    },
569
570    /**
571     * @param {!WebInspector.Event} event
572     */
573    _commandEvaluated: function(event)
574    {
575        this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.CommandEvaluated, event.data);
576    },
577
578    __proto__: WebInspector.Object.prototype
579}
580
581/**
582 * @type {!WebInspector.MultitargetConsoleModel}
583 */
584WebInspector.multitargetConsoleModel;
585