1/*
2 * Copyright (C) 2012 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 * @param {!WebInspector.Workspace} workspace
34 */
35WebInspector.PresentationConsoleMessageHelper = function(workspace)
36{
37    this._workspace = workspace;
38
39    /** @type {!Object.<string, !Array.<!WebInspector.ConsoleMessage>>} */
40    this._pendingConsoleMessages = {};
41
42    /** @type {!Array.<!WebInspector.PresentationConsoleMessage>} */
43    this._presentationConsoleMessages = [];
44
45    /** @type {!Map.<!WebInspector.UISourceCode, !Array.<!WebInspector.PresentationConsoleMessage>>} */
46    this._uiSourceCodeToMessages = new Map();
47
48    /** @type {!Map.<!WebInspector.UISourceCode, !WebInspector.Object>} */
49    this._uiSourceCodeToEventTarget = new Map();
50
51    workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
52    workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
53    WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
54    WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this);
55    WebInspector.multitargetConsoleModel.messages().forEach(this._consoleMessageAdded, this);
56    WebInspector.targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this);
57    WebInspector.targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._parsedScriptSource, this);
58    WebInspector.targetManager.addModelListener(WebInspector.DebuggerModel, WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this);
59}
60
61/**
62 * @enum {string}
63 */
64WebInspector.PresentationConsoleMessageHelper.Events = {
65    ConsoleMessageAdded: "ConsoleMessageAdded",
66    ConsoleMessageRemoved: "ConsoleMessageRemoved",
67    ConsoleMessagesCleared: "ConsoleMessagesCleared",
68}
69
70WebInspector.PresentationConsoleMessageHelper.prototype = {
71    /**
72     * @param {!WebInspector.PresentationConsoleMessageHelper.Events} eventType
73     * @param {!WebInspector.UISourceCode} uiSourceCode
74     * @param {function(!WebInspector.Event)} listener
75     * @param {!Object=} thisObject
76     */
77    addConsoleMessageEventListener: function(eventType, uiSourceCode, listener, thisObject)
78    {
79        var target = this._uiSourceCodeToEventTarget.get(uiSourceCode);
80        if (!target) {
81            target = new WebInspector.Object();
82            this._uiSourceCodeToEventTarget.set(uiSourceCode, target);
83        }
84        target.addEventListener(eventType, listener, thisObject);
85    },
86
87    /**
88     * @param {!WebInspector.PresentationConsoleMessageHelper.Events} eventType
89     * @param {!WebInspector.UISourceCode} uiSourceCode
90     * @param {function(!WebInspector.Event)} listener
91     * @param {!Object=} thisObject
92     */
93    removeConsoleMessageEventListener: function(eventType, uiSourceCode, listener, thisObject)
94    {
95        var target = this._uiSourceCodeToEventTarget.get(uiSourceCode);
96        if (!target)
97            return;
98        target.removeEventListener(eventType, listener, thisObject);
99    },
100
101    /**
102     * @param {!WebInspector.UISourceCode} uiSourceCode
103     * @return {!Array.<!WebInspector.PresentationConsoleMessage>}
104     */
105    consoleMessages: function(uiSourceCode)
106    {
107        return this._uiSourceCodeToMessages.get(uiSourceCode) || [];
108    },
109
110    /**
111     * @param {!WebInspector.PresentationConsoleMessageHelper.Events} eventType
112     * @param {!WebInspector.UISourceCode} uiSourceCode
113     * @param {!WebInspector.PresentationConsoleMessage=} message
114     */
115    _dispatchConsoleEvent: function(eventType, uiSourceCode, message)
116    {
117        var target = this._uiSourceCodeToEventTarget.get(uiSourceCode);
118        if (!target)
119            return;
120        target.dispatchEventToListeners(eventType, message);
121    },
122
123    /**
124     * @param {!WebInspector.Event} event
125     */
126    _uiSourceCodeRemoved: function(event)
127    {
128        var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
129        this._uiSourceCodeToEventTarget.remove(uiSourceCode);
130        this._uiSourceCodeToMessages.remove(uiSourceCode);
131    },
132
133    /**
134     * @param {!WebInspector.Event} event
135     */
136    _projectRemoved: function(event)
137    {
138        var project = /** @type {!WebInspector.Project} */ (event.data);
139        var uiSourceCodes = project.uiSourceCodes();
140        for (var i = 0; i < uiSourceCodes.length; ++i) {
141            this._uiSourceCodeToEventTarget.remove(uiSourceCodes[i]);
142            this._uiSourceCodeToMessages.remove(uiSourceCodes[i]);
143        }
144    },
145
146    /**
147     * @param {!WebInspector.Event} event
148     */
149    _onConsoleMessageAdded: function(event)
150    {
151        var message = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
152        this._consoleMessageAdded(message)
153    },
154
155    /**
156     * @param {!WebInspector.ConsoleMessage} message
157     */
158    _consoleMessageAdded: function(message)
159    {
160        if (!message.url || !message.isErrorOrWarning())
161            return;
162
163        var rawLocation = this._rawLocation(message);
164        if (rawLocation)
165            this._addConsoleMessageToScript(message, rawLocation);
166        else
167            this._addPendingConsoleMessage(message);
168    },
169
170    /**
171     * @param {!WebInspector.ConsoleMessage} message
172     * @return {?WebInspector.DebuggerModel.Location}
173     */
174    _rawLocation: function(message)
175    {
176        // FIXME(62725): stack trace line/column numbers are one-based.
177        var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1;
178        var columnNumber = message.stackTrace && message.stackTrace[0].columnNumber ? message.stackTrace[0].columnNumber - 1 : 0;
179        if (message.scriptId)
180            return message.target().debuggerModel.createRawLocationByScriptId(message.scriptId, message.url || "", lineNumber, columnNumber);
181        return message.target().debuggerModel.createRawLocationByURL(message.url || "", lineNumber, columnNumber);
182    },
183
184    /**
185     * @param {!WebInspector.ConsoleMessage} message
186     * @param {!WebInspector.DebuggerModel.Location} rawLocation
187     */
188    _addConsoleMessageToScript: function(message, rawLocation)
189    {
190        this._presentationConsoleMessages.push(new WebInspector.PresentationConsoleMessage(message, rawLocation));
191    },
192
193    /**
194     * @param {!WebInspector.ConsoleMessage} message
195     */
196    _addPendingConsoleMessage: function(message)
197    {
198        if (!message.url)
199            return;
200        if (!this._pendingConsoleMessages[message.url])
201            this._pendingConsoleMessages[message.url] = [];
202        this._pendingConsoleMessages[message.url].push(message);
203    },
204
205    /**
206     * @param {!WebInspector.Event} event
207     */
208    _parsedScriptSource: function(event)
209    {
210        var script = /** @type {!WebInspector.Script} */ (event.data);
211
212        var messages = this._pendingConsoleMessages[script.sourceURL];
213        if (!messages)
214            return;
215
216        var pendingMessages = [];
217        for (var i = 0; i < messages.length; i++) {
218            var message = messages[i];
219            var rawLocation = this._rawLocation(message);
220            if (script.target() === message.target() && script.scriptId === rawLocation.scriptId)
221                this._addConsoleMessageToScript(message, rawLocation);
222            else
223                pendingMessages.push(message);
224        }
225
226        if (pendingMessages.length)
227            this._pendingConsoleMessages[script.sourceURL] = pendingMessages;
228        else
229            delete this._pendingConsoleMessages[script.sourceURL];
230    },
231
232    /**
233     * @param {!WebInspector.PresentationConsoleMessage} message
234     */
235    _presentationConsoleMessageAdded: function(message)
236    {
237        var uiSourceCode = message._uiLocation.uiSourceCode;
238        var messages = this._uiSourceCodeToMessages.get(uiSourceCode);
239        if (!messages) {
240            messages = [];
241            this._uiSourceCodeToMessages.set(uiSourceCode, messages);
242        }
243        messages.push(message);
244        this._dispatchConsoleEvent(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageAdded, uiSourceCode, message);
245    },
246
247    /**
248     * @param {!WebInspector.PresentationConsoleMessage} message
249     */
250    _presentationConsoleMessageRemoved: function(message)
251    {
252        var uiSourceCode = message._uiLocation.uiSourceCode;
253        var messages = this._uiSourceCodeToMessages.get(uiSourceCode);
254        if (!messages)
255            return;
256        messages.remove(message);
257        this._dispatchConsoleEvent(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageRemoved, uiSourceCode, message);
258    },
259
260    _consoleCleared: function()
261    {
262        this._pendingConsoleMessages = {};
263        for (var i = 0; i < this._presentationConsoleMessages.length; ++i)
264            this._presentationConsoleMessages[i].dispose();
265        this._presentationConsoleMessages = [];
266        var targets = this._uiSourceCodeToEventTarget.values();
267        for (var i = 0; i < targets.length; ++i)
268            targets[i].dispatchEventToListeners(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessagesCleared);
269        this._uiSourceCodeToMessages.clear();
270    },
271
272    _debuggerReset: function()
273    {
274        this._pendingConsoleMessages = {};
275        this._presentationConsoleMessages = [];
276    }
277}
278
279/**
280 * @constructor
281 * @param {!WebInspector.ConsoleMessage} message
282 * @param {!WebInspector.DebuggerModel.Location} rawLocation
283 */
284WebInspector.PresentationConsoleMessage = function(message, rawLocation)
285{
286    this.originalMessage = message;
287    this._liveLocation = WebInspector.debuggerWorkspaceBinding.createLiveLocation(rawLocation, this._updateLocation.bind(this));
288}
289
290WebInspector.PresentationConsoleMessage.prototype = {
291    /**
292     * @param {!WebInspector.UILocation} uiLocation
293     */
294    _updateLocation: function(uiLocation)
295    {
296        if (this._uiLocation)
297            WebInspector.presentationConsoleMessageHelper._presentationConsoleMessageRemoved(this);
298        this._uiLocation = uiLocation;
299        WebInspector.presentationConsoleMessageHelper._presentationConsoleMessageAdded(this);
300    },
301
302    get lineNumber()
303    {
304        return this._uiLocation.lineNumber;
305    },
306
307    dispose: function()
308    {
309        this._liveLocation.dispose();
310    }
311}
312
313/** @type {!WebInspector.PresentationConsoleMessageHelper} */
314WebInspector.presentationConsoleMessageHelper;