Fix an exception and remove code that was missed when TextPrompt
[WebKit-https.git] / WebCore / page / inspector / Console.js
1 /*
2  * Copyright (C) 2007, 2008 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.messagesElement = document.createElement("div");
36     this.messagesElement.id = "console-messages";
37     this.messagesElement.addEventListener("selectstart", this.messagesSelectStart.bind(this), true);
38     this.messagesElement.addEventListener("click", this.messagesClicked.bind(this), true);
39     this.element.appendChild(this.messagesElement);
40
41     this.promptElement = document.createElement("div");
42     this.promptElement.id = "console-prompt";
43     this.promptElement.addEventListener("keydown", this.promptKeyDown.bind(this), false);
44     this.promptElement.appendChild(document.createElement("br"));
45     this.messagesElement.appendChild(this.promptElement);
46
47     this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " .=:[({;");
48
49     this.clearButton = document.createElement("button");
50     this.clearButton.title = WebInspector.UIString("Clear");
51     this.clearButton.textContent = WebInspector.UIString("Clear");
52     this.clearButton.addEventListener("click", this.clearButtonClicked.bind(this), false);
53 }
54
55 WebInspector.ConsolePanel.prototype = {
56     show: function()
57     {
58         WebInspector.Panel.prototype.show.call(this);
59         WebInspector.consoleListItem.select();
60
61         this.clearButton.removeStyleClass("hidden");
62         if (!this.clearButton.parentNode)
63             document.getElementById("toolbarButtons").appendChild(this.clearButton);
64
65         WebInspector.currentFocusElement = document.getElementById("main");
66
67         function focusPrompt()
68         {
69             if (!this.prompt.isCaretInsidePrompt())
70                 this.prompt.moveCaretToEndOfPrompt();
71         }
72
73         setTimeout(focusPrompt.bind(this), 0);
74     },
75
76     hide: function()
77     {
78         WebInspector.Panel.prototype.hide.call(this);
79         WebInspector.consoleListItem.deselect();
80         this.clearButton.addStyleClass("hidden");
81     },
82
83     addMessage: function(msg)
84     {
85         if (msg.url in WebInspector.resourceURLMap) {
86             msg.resource = WebInspector.resourceURLMap[msg.url];
87             switch (msg.level) {
88                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
89                     ++msg.resource.warnings;
90                     if (msg.resource.panel.addMessageToSource)
91                         msg.resource.panel.addMessageToSource(msg);
92                     break;
93                 case WebInspector.ConsoleMessage.MessageLevel.Error:
94                     ++msg.resource.errors;
95                     if (msg.resource.panel.addMessageToSource)
96                         msg.resource.panel.addMessageToSource(msg);
97                     break;
98             }
99         }
100
101         this.messages.push(msg);
102
103         var element = msg.toMessageElement();
104         this.messagesElement.insertBefore(element, this.promptElement);
105         this.promptElement.scrollIntoView(false);
106     },
107
108     clearMessages: function()
109     {
110         for (var i = 0; i < this.messages.length; ++i) {
111             var resource = this.messages[i].resource;
112             if (!resource)
113                 continue;
114             resource.errors = 0;
115             resource.warnings = 0;
116         }
117
118         this.messages = [];
119
120         while (this.messagesElement.firstChild != this.promptElement)
121             this.messagesElement.removeChild(this.messagesElement.firstChild);
122     },
123
124     completions: function(wordRange, bestMatchOnly)
125     {
126         // Pass less characters to scanBackwards so the range will be a more complete expression.
127         var expression = this.prompt.scanBackwards(" =:{;", wordRange.startContainer, wordRange.startOffset);
128         var expressionString = expression.toString();
129         var lastIndex = expressionString.length - 1;
130
131         var dotNotation = (expressionString[lastIndex] === ".");
132         var bracketNotation = (expressionString[lastIndex] === "[");
133
134         if (dotNotation || bracketNotation)
135             expressionString = expressionString.substr(0, lastIndex);
136
137         var prefix = wordRange.toString();
138         if (!expressionString && !prefix)
139             return;
140
141         var result = InspectorController.inspectedWindow();
142         if (expressionString) {
143             try {
144                 result = this._evalInInspectedWindow(expressionString);
145             } catch(e) {
146                 // Do nothing, the prefix will be considered a window property.
147             }
148         }
149
150         if (bracketNotation) {
151             if (prefix.length && prefix[0] === "'")
152                 var quoteUsed = "'";
153             else
154                 var quoteUsed = "\"";
155         }
156
157         var results = [];
158         var properties = Object.sortedProperties(result);
159         for (var i = 0; i < properties.length; ++i) {
160             var property = properties[i];
161             if (bracketNotation)
162                 property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed + "]";
163             if (property.length < prefix.length)
164                 continue;
165             if (property.indexOf(prefix) !== 0)
166                 continue;
167             results.push(property);
168             if (bestMatchOnly)
169                 break;
170         }
171
172         return results;
173     },
174
175     clearButtonClicked: function()
176     {
177         this.clearMessages();
178     },
179
180     messagesSelectStart: function(event)
181     {
182         if (this._selectionTimeout)
183             clearTimeout(this._selectionTimeout);
184
185         this.prompt.clearAutoComplete();
186
187         function moveBackIfOutside()
188         {
189             delete this._selectionTimeout;
190             if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
191                 this.prompt.moveCaretToEndOfPrompt();
192             this.prompt.autoCompleteSoon();
193         }
194
195         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
196     },
197
198     messagesClicked: function(event)
199     {
200         var link = event.target.enclosingNodeOrSelfWithNodeName("a");
201         if (link && link.representedNode) {
202             WebInspector.updateFocusedNode(link.representedNode);
203             return;
204         }
205
206         var messageElement = event.target.enclosingNodeOrSelfWithClass("console-message");
207         if (!messageElement)
208             return;
209
210         if (!messageElement.message)
211             return;
212
213         var resource = messageElement.message.resource;
214         if (!resource)
215             return;
216
217         if (link && link.hasStyleClass("console-message-url")) {
218             WebInspector.navigateToResource(resource);
219             resource.panel.showSourceLine(item.message.line);
220         }
221
222         event.stopPropagation();
223         event.preventDefault();
224     },
225
226     promptKeyDown: function(event)
227     {
228         switch (event.keyIdentifier) {
229             case "Enter":
230                 this._enterKeyPressed(event);
231                 return;
232         }
233
234         this.prompt.handleKeyEvent(event);
235     },
236
237     _evalInInspectedWindow: function(expression)
238     {
239         return InspectorController.inspectedWindow().eval(expression);
240     },
241
242     _enterKeyPressed: function(event)
243     {
244         event.preventDefault();
245         event.stopPropagation();
246
247         this.prompt.clearAutoComplete(true);
248
249         var str = this.prompt.text;
250         if (!str.length)
251             return;
252
253         var result;
254         var exception = false;
255         try {
256             result = this._evalInInspectedWindow(str);
257         } catch(e) {
258             result = e;
259             exception = true;
260         }
261
262         this.prompt.history.push(str);
263         this.prompt.historyOffset = 0;
264         this.prompt.text = "";
265
266         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
267         this.addMessage(new WebInspector.ConsoleCommand(str, result, this._format(result), level));
268     },
269
270     _format: function(output)
271     {
272         var type = Object.type(output);
273         if (type === "object") {
274             if (output instanceof Node)
275                 type = "node";
276         }
277
278         // We don't perform any special formatting on these types, so we just
279         // pass them through the simple _formatvalue function.
280         var undecoratedTypes = {
281             "undefined": 1,
282             "null": 1,
283             "boolean": 1,
284             "number": 1,
285             "date": 1,
286             "function": 1,
287         };
288
289         var formatter;
290         if (type in undecoratedTypes)
291             formatter = "_formatvalue";
292         else {
293             formatter = "_format" + type;
294             if (!(formatter in this)) {
295                 formatter = "_formatobject";
296                 type = "object";
297             }
298         }
299
300         var span = document.createElement("span");
301         span.addStyleClass("console-formatted-" + type);
302         this[formatter](output, span);
303         return span;
304     },
305
306     _formatvalue: function(val, elem)
307     {
308         elem.appendChild(document.createTextNode(val));
309     },
310
311     _formatstring: function(str, elem)
312     {
313         elem.appendChild(document.createTextNode("\"" + str + "\""));
314     },
315
316     _formatregexp: function(re, elem)
317     {
318         var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
319         elem.appendChild(document.createTextNode(formatted));
320     },
321
322     _formatarray: function(arr, elem)
323     {
324         elem.appendChild(document.createTextNode("["));
325         for (var i = 0; i < arr.length; ++i) {
326             elem.appendChild(this._format(arr[i]));
327             if (i < arr.length - 1)
328                 elem.appendChild(document.createTextNode(", "));
329         }
330         elem.appendChild(document.createTextNode("]"));
331     },
332
333     _formatnode: function(node, elem)
334     {
335         var anchor = document.createElement("a");
336         anchor.innerHTML = nodeTitleInfo.call(node).title;
337         anchor.representedNode = node;
338         anchor.addEventListener("mouseover", function() { InspectorController.highlightDOMNode(node) }, false);
339         anchor.addEventListener("mouseout", function() { InspectorController.hideDOMNodeHighlight() }, false);
340         elem.appendChild(anchor);
341     },
342
343     _formatobject: function(obj, elem)
344     {
345         elem.appendChild(document.createTextNode(Object.describe(obj)));
346     },
347 }
348
349 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
350
351 WebInspector.ConsoleMessage = function(source, level, message, line, url)
352 {
353     this.source = source;
354     this.level = level;
355     this.message = message;
356     this.line = line;
357     this.url = url;
358 }
359
360 WebInspector.ConsoleMessage.prototype = {
361     get shortURL()
362     {
363         if (this.resource)
364             return this.resource.displayName;
365         return this.url;
366     },
367
368     toMessageElement: function()
369     {
370         var element = document.createElement("div");
371         element.message = this;
372         element.className = "console-message";
373
374         switch (this.source) {
375             case WebInspector.ConsoleMessage.MessageSource.HTML:
376                 element.addStyleClass("console-html-source");
377                 break;
378             case WebInspector.ConsoleMessage.MessageSource.XML:
379                 element.addStyleClass("console-xml-source");
380                 break;
381             case WebInspector.ConsoleMessage.MessageSource.JS:
382                 element.addStyleClass("console-js-source");
383                 break;
384             case WebInspector.ConsoleMessage.MessageSource.CSS:
385                 element.addStyleClass("console-css-source");
386                 break;
387             case WebInspector.ConsoleMessage.MessageSource.Other:
388                 element.addStyleClass("console-other-source");
389                 break;
390         }
391
392         switch (this.level) {
393             case WebInspector.ConsoleMessage.MessageLevel.Tip:
394                 element.addStyleClass("console-tip-level");
395                 break;
396             case WebInspector.ConsoleMessage.MessageLevel.Log:
397                 element.addStyleClass("console-log-level");
398                 break;
399             case WebInspector.ConsoleMessage.MessageLevel.Warning:
400                 element.addStyleClass("console-warning-level");
401                 break;
402             case WebInspector.ConsoleMessage.MessageLevel.Error:
403                 element.addStyleClass("console-error-level");
404         }
405
406         var messageTextElement = document.createElement("span");
407         messageTextElement.className = "console-message-text";
408         messageTextElement.textContent = this.message;
409         element.appendChild(messageTextElement);
410
411         element.appendChild(document.createTextNode(" "));
412
413         if (this.url && this.url !== "undefined") {
414             var urlElement = document.createElement("a");
415             urlElement.className = "console-message-url";
416
417             if (this.line > 0)
418                 urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line);
419             else
420                 urlElement.textContent = this.url;
421
422             element.appendChild(urlElement);
423         }
424
425         return element;
426     },
427
428     toString: function()
429     {
430         var sourceString;
431         switch (this.source) {
432             case WebInspector.ConsoleMessage.MessageSource.HTML:
433                 sourceString = "HTML";
434                 break;
435             case WebInspector.ConsoleMessage.MessageSource.XML:
436                 sourceString = "XML";
437                 break;
438             case WebInspector.ConsoleMessage.MessageSource.JS:
439                 sourceString = "JS";
440                 break;
441             case WebInspector.ConsoleMessage.MessageSource.CSS:
442                 sourceString = "CSS";
443                 break;
444             case WebInspector.ConsoleMessage.MessageSource.Other:
445                 sourceString = "Other";
446                 break;
447         }
448
449         var levelString;
450         switch (this.level) {
451             case WebInspector.ConsoleMessage.MessageLevel.Tip:
452                 levelString = "Tip";
453                 break;
454             case WebInspector.ConsoleMessage.MessageLevel.Log:
455                 levelString = "Log";
456                 break;
457             case WebInspector.ConsoleMessage.MessageLevel.Warning:
458                 levelString = "Warning";
459                 break;
460             case WebInspector.ConsoleMessage.MessageLevel.Error:
461                 levelString = "Error";
462                 break;
463         }
464
465         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
466     }
467 }
468
469 // Note: Keep these constants in sync with the ones in Chrome.h
470 WebInspector.ConsoleMessage.MessageSource = {
471     HTML: 0,
472     XML: 1,
473     JS: 2,
474     CSS: 3,
475     Other: 4,
476 }
477
478 WebInspector.ConsoleMessage.MessageLevel = {
479     Tip: 0,
480     Log: 1,
481     Warning: 2,
482     Error: 3
483 }
484
485 WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level)
486 {
487     this.command = command;
488     this.formattedResultElement = formattedResultElement;
489     this.level = level;
490 }
491
492 WebInspector.ConsoleCommand.prototype = {
493     toMessageElement: function()
494     {
495         var element = document.createElement("div");
496         element.command = this;
497         element.className = "console-user-command";
498
499         var commandTextElement = document.createElement("span");
500         commandTextElement.className = "console-message-text";
501         commandTextElement.textContent = this.command;
502         element.appendChild(commandTextElement);
503
504         var resultElement = document.createElement("div");
505         resultElement.className = "console-message";
506         element.appendChild(resultElement);
507
508         switch (this.level) {
509             case WebInspector.ConsoleMessage.MessageLevel.Log:
510                 resultElement.addStyleClass("console-log-level");
511                 break;
512             case WebInspector.ConsoleMessage.MessageLevel.Warning:
513                 resultElement.addStyleClass("console-warning-level");
514                 break;
515             case WebInspector.ConsoleMessage.MessageLevel.Error:
516                 resultElement.addStyleClass("console-error-level");
517         }
518
519         var resultTextElement = document.createElement("span");
520         resultTextElement.className = "console-message-text";
521         resultTextElement.appendChild(this.formattedResultElement);
522         resultElement.appendChild(resultTextElement);
523
524         return element;
525     }
526 }