Reviewed by Anders Carlsson.
[WebKit-https.git] / WebCore / page / inspector / ConsolePanel.js
1 /*
2  * Copyright (C) 2007 Apple 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
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 WebInspector.ConsolePanel = function()
30 {
31     WebInspector.Panel.call(this);
32
33     this.messages = [];
34
35     this.commandHistory = [];
36     this.commandOffset = 0;
37
38     this.messageList = document.createElement("ol");
39     this.messageList.className = "console-message-list";
40     this.element.appendChild(this.messageList);
41
42     this.messageList.addEventListener("click", this.messageListClicked.bind(this), true);
43
44     this.consolePrompt = document.createElement("textarea");
45     this.consolePrompt.className = "console-prompt";
46     this.element.appendChild(this.consolePrompt);
47
48     this.consolePrompt.addEventListener("keydown", this.promptKeyDown.bind(this), false);
49
50     var clearButtonText = WebInspector.UIString("Clear");
51     this.clearMessagesElement = document.createElement("button");
52     this.clearMessagesElement.appendChild(document.createTextNode(clearButtonText));
53     this.clearMessagesElement.title = clearButtonText;
54     this.clearMessagesElement.addEventListener("click", this.clearButtonClicked.bind(this), false);
55 }
56
57 WebInspector.ConsolePanel.prototype = {
58     show: function()
59     {
60         WebInspector.Panel.prototype.show.call(this);
61         WebInspector.consoleListItem.select();
62
63         this.clearMessagesElement.removeStyleClass("hidden");
64         if (!this.clearMessagesElement.parentNode)
65             document.getElementById("toolbarButtons").appendChild(this.clearMessagesElement);
66     },
67
68     hide: function()
69     {
70         WebInspector.Panel.prototype.hide.call(this);
71         WebInspector.consoleListItem.deselect();
72         this.clearMessagesElement.addStyleClass("hidden");
73     },
74
75     addMessage: function(msg)
76     {
77         if (msg.url in WebInspector.resourceURLMap) {
78             msg.resource = WebInspector.resourceURLMap[msg.url];
79             switch (msg.level) {
80                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
81                     ++msg.resource.warnings;
82                     msg.resource.panel.addMessageToSource(msg);
83                     break;
84                 case WebInspector.ConsoleMessage.MessageLevel.Error:
85                     ++msg.resource.errors;
86                     msg.resource.panel.addMessageToSource(msg);
87                     break;
88             }
89         }
90         this.messages.push(msg);
91
92         var item = msg.toListItem();
93         item.message = msg;
94         this.messageList.appendChild(item);
95         item.scrollIntoView(false);
96     },
97
98     clearMessages: function()
99     {
100         for (var i = 0; i < this.messages.length; ++i) {
101             var resource = this.messages[i].resource;
102             if (!resource)
103                 continue;
104
105             resource.errors = 0;
106             resource.warnings = 0;
107         }
108
109         this.messages = [];
110         this.messageList.removeChildren();
111     },
112
113     clearButtonClicked: function()
114     {
115         this.clearMessages();
116     },
117
118     messageListClicked: function(event)
119     {
120         var link = event.target.firstParentOrSelfWithNodeName("a");
121         if (link) {
122             WebInspector.updateFocusedNode(link.representedNode);
123             return;
124         }
125
126         var item = event.target.firstParentOrSelfWithNodeName("li");
127         if (!item)
128             return;
129
130         var resource = item.message.resource;
131         if (!resource)
132             return;
133
134         resource.panel.showSourceLine(item.message.line);
135
136         event.stopPropagation();
137         event.preventDefault();
138     },
139
140     promptKeyDown: function(event)
141     {
142         switch (event.keyIdentifier) {
143             case "Enter":
144                 this._onEnterPressed(event);
145                 break;
146             case "Up":
147                 this._onUpPressed(event);
148                 break;
149             case "Down":
150                 this._onDownPressed(event);
151                 break;
152         }
153     },
154
155     _onEnterPressed: function(event)
156     {
157         event.preventDefault();
158         event.stopPropagation();
159
160         var str = this.consolePrompt.value;
161         if (!str.length)
162             return;
163
164         this.commandHistory.push(str);
165         this.commandOffset = 0;
166
167         this.consolePrompt.value = "";
168
169         var result;
170         var exception = false;
171         try {
172             // This with block is needed to work around http://bugs.webkit.org/show_bug.cgi?id=11399
173             with (InspectorController.inspectedWindow()) {
174                 result = eval(str);
175             }
176         } catch(e) {
177             result = e;
178             exception = true;
179         }
180
181         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
182
183         this.addMessage(new WebInspector.ConsoleCommand(str, this._outputToNode(result)));
184     },
185
186     _onUpPressed: function(event)
187     {
188         event.preventDefault();
189         event.stopPropagation();
190
191         if (this.commandOffset == this.commandHistory.length)
192             return;
193
194         if (this.commandOffset == 0)
195             this.tempSavedCommand = this.consolePrompt.value;
196
197         ++this.commandOffset;
198         this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset];
199         this.consolePrompt.moveCursorToEnd();
200     },
201
202     _onDownPressed: function(event)
203     {
204         event.preventDefault();
205         event.stopPropagation();
206
207         if (this.commandOffset == 0)
208             return;
209
210         --this.commandOffset;
211
212         if (this.commandOffset == 0) {
213             this.consolePrompt.value = this.tempSavedCommand;
214             this.consolePrompt.moveCursorToEnd();
215             delete this.tempSavedCommand;
216             return;
217         }
218
219         this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset];
220         this.consolePrompt.moveCursorToEnd();
221     },
222
223     _outputToNode: function(output)
224     {
225         if (output instanceof Node) {
226             var anchor = document.createElement("a");
227             anchor.innerHTML = output.titleInfo().title;
228             anchor.representedNode = output;
229             return anchor;
230         }
231         return document.createTextNode(Object.describe(output));
232     }
233 }
234
235 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
236
237 WebInspector.ConsoleMessage = function(source, level, message, line, url)
238 {
239     this.source = source;
240     this.level = level;
241     this.message = message;
242     this.line = line;
243     this.url = url;
244 }
245
246 WebInspector.ConsoleMessage.prototype = {
247     get shortURL()
248     {
249         if (this.resource)
250             return this.resource.displayName;
251         return this.url;
252     },
253
254     toListItem: function()
255     {
256         var item = document.createElement("li");
257         item.className = "console-message";
258         switch (this.source) {
259             case WebInspector.ConsoleMessage.MessageSource.HTML:
260                 item.className += " console-html-source";
261                 break;
262             case WebInspector.ConsoleMessage.MessageSource.XML:
263                 item.className += " console-xml-source";
264                 break;
265             case WebInspector.ConsoleMessage.MessageSource.JS:
266                 item.className += " console-js-source";
267                 break;
268             case WebInspector.ConsoleMessage.MessageSource.CSS:
269                 item.className += " console-css-source";
270                 break;
271             case WebInspector.ConsoleMessage.MessageSource.Other:
272                 item.className += " console-other-source";
273                 break;
274         }
275
276         switch (this.level) {
277             case WebInspector.ConsoleMessage.MessageLevel.Tip:
278                 item.className += " console-tip-level";
279                 break;
280             case WebInspector.ConsoleMessage.MessageLevel.Log:
281                 item.className += " console-log-level";
282                 break;
283             case WebInspector.ConsoleMessage.MessageLevel.Warning:
284                 item.className += " console-warning-level";
285                 break;
286             case WebInspector.ConsoleMessage.MessageLevel.Error:
287                 item.className += " console-error-level";
288         }
289
290
291         var messageDiv = document.createElement("div");
292         messageDiv.className = "console-message-message";
293         messageDiv.textContent = this.message;
294         item.appendChild(messageDiv);
295
296         var urlDiv = document.createElement("div");
297         urlDiv.className = "console-message-url";
298         urlDiv.textContent = this.url;
299         item.appendChild(urlDiv);
300
301         if (this.line) {
302             var lineDiv = document.createElement("div");
303             lineDiv.className = "console-message-line";
304             lineDiv.textContent = this.line;
305             item.appendChild(lineDiv);
306         }
307
308         return item;
309     },
310
311     toString: function()
312     {
313         var sourceString;
314         switch (this.source) {
315             case WebInspector.ConsoleMessage.MessageSource.HTML:
316                 sourceString = "HTML";
317                 break;
318             case WebInspector.ConsoleMessage.MessageSource.XML:
319                 sourceString = "XML";
320                 break;
321             case WebInspector.ConsoleMessage.MessageSource.JS:
322                 sourceString = "JS";
323                 break;
324             case WebInspector.ConsoleMessage.MessageSource.CSS:
325                 sourceString = "CSS";
326                 break;
327             case WebInspector.ConsoleMessage.MessageSource.Other:
328                 sourceString = "Other";
329                 break;
330         }
331
332         var levelString;
333         switch (this.level) {
334             case WebInspector.ConsoleMessage.MessageLevel.Tip:
335                 levelString = "Tip";
336                 break;
337             case WebInspector.ConsoleMessage.MessageLevel.Log:
338                 levelString = "Log";
339                 break;
340             case WebInspector.ConsoleMessage.MessageLevel.Warning:
341                 levelString = "Warning";
342                 break;
343             case WebInspector.ConsoleMessage.MessageLevel.Error:
344                 levelString = "Error";
345                 break;
346         }
347
348         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
349     }
350 }
351
352 // Note: Keep these constants in sync with the ones in Chrome.h
353 WebInspector.ConsoleMessage.MessageSource = {
354     HTML: 0,
355     XML: 1,
356     JS: 2,
357     CSS: 3,
358     Other: 4,
359 };
360
361 WebInspector.ConsoleMessage.MessageLevel = {
362     Tip: 0,
363     Log: 1,
364     Warning: 2,
365     Error: 3,
366 };
367
368 WebInspector.ConsoleCommand = function(input, output)
369 {
370     this.input = input;
371     this.output = output;
372 }
373
374 WebInspector.ConsoleCommand.prototype = {
375     toListItem: function()
376     {
377         var item = document.createElement("li");
378         item.className = "console-command";
379
380         var inputDiv = document.createElement("div");
381         inputDiv.className = "console-command-input";
382         inputDiv.textContent = this.input;
383         item.appendChild(inputDiv);
384
385         var outputDiv = document.createElement("div");
386         outputDiv.className = "console-command-output";
387         outputDiv.appendChild(this.output);
388         item.appendChild(outputDiv);
389
390         return item;
391     }
392 }