57bf331ef0e4768d0455f33fedf299c91fd82fdd
[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 && link.representedNode) {
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         if (link && link.hasStyleClass("console-message-url")) {
135             WebInspector.navigateToResource(resource);
136             resource.panel.showSourceLine(item.message.line);
137         }
138
139         event.stopPropagation();
140         event.preventDefault();
141     },
142
143     promptKeyDown: function(event)
144     {
145         switch (event.keyIdentifier) {
146             case "Enter":
147                 this._onEnterPressed(event);
148                 break;
149             case "Up":
150                 this._onUpPressed(event);
151                 break;
152             case "Down":
153                 this._onDownPressed(event);
154                 break;
155         }
156     },
157
158     _onEnterPressed: function(event)
159     {
160         event.preventDefault();
161         event.stopPropagation();
162
163         var str = this.consolePrompt.value;
164         if (!str.length)
165             return;
166
167         this.commandHistory.push(str);
168         this.commandOffset = 0;
169
170         this.consolePrompt.value = "";
171
172         var result;
173         var exception = false;
174         try {
175             // This with block is needed to work around http://bugs.webkit.org/show_bug.cgi?id=11399
176             with (InspectorController.inspectedWindow()) {
177                 result = eval(str);
178             }
179         } catch(e) {
180             result = e;
181             exception = true;
182         }
183
184         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
185
186         this.addMessage(new WebInspector.ConsoleCommand(str, this._format(result)));
187     },
188
189     _onUpPressed: function(event)
190     {
191         event.preventDefault();
192         event.stopPropagation();
193
194         if (this.commandOffset == this.commandHistory.length)
195             return;
196
197         if (this.commandOffset == 0)
198             this.tempSavedCommand = this.consolePrompt.value;
199
200         ++this.commandOffset;
201         this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset];
202         this.consolePrompt.moveCursorToEnd();
203     },
204
205     _onDownPressed: function(event)
206     {
207         event.preventDefault();
208         event.stopPropagation();
209
210         if (this.commandOffset == 0)
211             return;
212
213         --this.commandOffset;
214
215         if (this.commandOffset == 0) {
216             this.consolePrompt.value = this.tempSavedCommand;
217             this.consolePrompt.moveCursorToEnd();
218             delete this.tempSavedCommand;
219             return;
220         }
221
222         this.consolePrompt.value = this.commandHistory[this.commandHistory.length - this.commandOffset];
223         this.consolePrompt.moveCursorToEnd();
224     },
225
226     _format: function(output)
227     {
228         var type = Object.type(output);
229         if (type === "object") {
230             if (output instanceof Node)
231                 type = "node";
232         }
233
234         // We don't perform any special formatting on these types, so we just
235         // pass them through the simple _formatvalue function.
236         var undecoratedTypes = {
237             "undefined": 1,
238             "null": 1,
239             "boolean": 1,
240             "number": 1,
241             "date": 1,
242             "function": 1,
243         };
244
245         var formatter;
246         if (type in undecoratedTypes)
247             formatter = "_formatvalue";
248         else {
249             formatter = "_format" + type;
250             if (!(formatter in this)) {
251                 formatter = "_formatobject";
252                 type = "object";
253             }
254         }
255
256         var span = document.createElement("span");
257         span.addStyleClass("console-formatted-" + type);
258         this[formatter](output, span);
259         return span;
260     },
261
262     _formatvalue: function(val, elem)
263     {
264         elem.appendChild(document.createTextNode(val));
265     },
266
267     _formatstring: function(str, elem)
268     {
269         elem.appendChild(document.createTextNode("\"" + str + "\""));
270     },
271
272     _formatregexp: function(re, elem)
273     {
274         var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
275         elem.appendChild(document.createTextNode(formatted));
276     },
277
278     _formatarray: function(arr, elem)
279     {
280         elem.appendChild(document.createTextNode("["));
281         for (var i = 0; i < arr.length; ++i) {
282             elem.appendChild(this._format(arr[i]));
283             if (i < arr.length - 1)
284                 elem.appendChild(document.createTextNode(", "));
285         }
286         elem.appendChild(document.createTextNode("]"));
287     },
288
289     _formatnode: function(node, elem)
290     {
291         var anchor = document.createElement("a");
292         anchor.innerHTML = node.titleInfo().title;
293         anchor.representedNode = node;
294         elem.appendChild(anchor);
295     },
296
297     _formatobject: function(obj, elem)
298     {
299         elem.appendChild(document.createTextNode(Object.describe(obj)));
300     },
301 }
302
303 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
304
305 WebInspector.ConsoleMessage = function(source, level, message, line, url)
306 {
307     this.source = source;
308     this.level = level;
309     this.message = message;
310     this.line = line;
311     this.url = url;
312 }
313
314 WebInspector.ConsoleMessage.prototype = {
315     get shortURL()
316     {
317         if (this.resource)
318             return this.resource.displayName;
319         return this.url;
320     },
321
322     toListItem: function()
323     {
324         var item = document.createElement("li");
325         item.className = "console-message";
326         switch (this.source) {
327             case WebInspector.ConsoleMessage.MessageSource.HTML:
328                 item.className += " console-html-source";
329                 break;
330             case WebInspector.ConsoleMessage.MessageSource.XML:
331                 item.className += " console-xml-source";
332                 break;
333             case WebInspector.ConsoleMessage.MessageSource.JS:
334                 item.className += " console-js-source";
335                 break;
336             case WebInspector.ConsoleMessage.MessageSource.CSS:
337                 item.className += " console-css-source";
338                 break;
339             case WebInspector.ConsoleMessage.MessageSource.Other:
340                 item.className += " console-other-source";
341                 break;
342         }
343
344         switch (this.level) {
345             case WebInspector.ConsoleMessage.MessageLevel.Tip:
346                 item.className += " console-tip-level";
347                 break;
348             case WebInspector.ConsoleMessage.MessageLevel.Log:
349                 item.className += " console-log-level";
350                 break;
351             case WebInspector.ConsoleMessage.MessageLevel.Warning:
352                 item.className += " console-warning-level";
353                 break;
354             case WebInspector.ConsoleMessage.MessageLevel.Error:
355                 item.className += " console-error-level";
356         }
357
358         var messageDiv = document.createElement("div");
359         messageDiv.className = "console-message-message";
360         messageDiv.textContent = this.message;
361         item.appendChild(messageDiv);
362
363         if (this.url && this.url !== "undefined") {
364             var urlElement = document.createElement("a");
365             urlElement.className = "console-message-url";
366
367             if (this.line > 0)
368                 urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line);
369             else
370                 urlElement.textContent = this.url;
371
372             item.appendChild(urlElement);
373         }
374
375         return item;
376     },
377
378     toString: function()
379     {
380         var sourceString;
381         switch (this.source) {
382             case WebInspector.ConsoleMessage.MessageSource.HTML:
383                 sourceString = "HTML";
384                 break;
385             case WebInspector.ConsoleMessage.MessageSource.XML:
386                 sourceString = "XML";
387                 break;
388             case WebInspector.ConsoleMessage.MessageSource.JS:
389                 sourceString = "JS";
390                 break;
391             case WebInspector.ConsoleMessage.MessageSource.CSS:
392                 sourceString = "CSS";
393                 break;
394             case WebInspector.ConsoleMessage.MessageSource.Other:
395                 sourceString = "Other";
396                 break;
397         }
398
399         var levelString;
400         switch (this.level) {
401             case WebInspector.ConsoleMessage.MessageLevel.Tip:
402                 levelString = "Tip";
403                 break;
404             case WebInspector.ConsoleMessage.MessageLevel.Log:
405                 levelString = "Log";
406                 break;
407             case WebInspector.ConsoleMessage.MessageLevel.Warning:
408                 levelString = "Warning";
409                 break;
410             case WebInspector.ConsoleMessage.MessageLevel.Error:
411                 levelString = "Error";
412                 break;
413         }
414
415         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
416     }
417 }
418
419 // Note: Keep these constants in sync with the ones in Chrome.h
420 WebInspector.ConsoleMessage.MessageSource = {
421     HTML: 0,
422     XML: 1,
423     JS: 2,
424     CSS: 3,
425     Other: 4,
426 };
427
428 WebInspector.ConsoleMessage.MessageLevel = {
429     Tip: 0,
430     Log: 1,
431     Warning: 2,
432     Error: 3,
433 };
434
435 WebInspector.ConsoleCommand = function(input, output)
436 {
437     this.input = input;
438     this.output = output;
439 }
440
441 WebInspector.ConsoleCommand.prototype = {
442     toListItem: function()
443     {
444         var item = document.createElement("li");
445         item.className = "console-command";
446
447         var inputDiv = document.createElement("div");
448         inputDiv.className = "console-command-input";
449         inputDiv.textContent = this.input;
450         item.appendChild(inputDiv);
451
452         var outputDiv = document.createElement("div");
453         outputDiv.className = "console-command-output";
454         outputDiv.appendChild(this.output);
455         item.appendChild(outputDiv);
456
457         return item;
458     }
459 }