Remove the workaround for bug 11399 now that it is fixed.
[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         this.commandHistory.push(str);
254         this.commandOffset = 0;
255
256         this.promptText = "";
257
258         var result;
259         var exception = false;
260         try {
261             result = this._evalInInspectedWindow(str);
262         } catch(e) {
263             result = e;
264             exception = true;
265         }
266
267         this.prompt.history.push(str);
268         this.prompt.historyOffset = 0;
269         this.prompt.text = "";
270
271         var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
272         this.addMessage(new WebInspector.ConsoleCommand(str, result, this._format(result), level));
273     },
274
275     _format: function(output)
276     {
277         var type = Object.type(output);
278         if (type === "object") {
279             if (output instanceof Node)
280                 type = "node";
281         }
282
283         // We don't perform any special formatting on these types, so we just
284         // pass them through the simple _formatvalue function.
285         var undecoratedTypes = {
286             "undefined": 1,
287             "null": 1,
288             "boolean": 1,
289             "number": 1,
290             "date": 1,
291             "function": 1,
292         };
293
294         var formatter;
295         if (type in undecoratedTypes)
296             formatter = "_formatvalue";
297         else {
298             formatter = "_format" + type;
299             if (!(formatter in this)) {
300                 formatter = "_formatobject";
301                 type = "object";
302             }
303         }
304
305         var span = document.createElement("span");
306         span.addStyleClass("console-formatted-" + type);
307         this[formatter](output, span);
308         return span;
309     },
310
311     _formatvalue: function(val, elem)
312     {
313         elem.appendChild(document.createTextNode(val));
314     },
315
316     _formatstring: function(str, elem)
317     {
318         elem.appendChild(document.createTextNode("\"" + str + "\""));
319     },
320
321     _formatregexp: function(re, elem)
322     {
323         var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1);
324         elem.appendChild(document.createTextNode(formatted));
325     },
326
327     _formatarray: function(arr, elem)
328     {
329         elem.appendChild(document.createTextNode("["));
330         for (var i = 0; i < arr.length; ++i) {
331             elem.appendChild(this._format(arr[i]));
332             if (i < arr.length - 1)
333                 elem.appendChild(document.createTextNode(", "));
334         }
335         elem.appendChild(document.createTextNode("]"));
336     },
337
338     _formatnode: function(node, elem)
339     {
340         var anchor = document.createElement("a");
341         anchor.innerHTML = nodeTitleInfo.call(node).title;
342         anchor.representedNode = node;
343         anchor.addEventListener("mouseover", function() { InspectorController.highlightDOMNode(node) }, false);
344         anchor.addEventListener("mouseout", function() { InspectorController.hideDOMNodeHighlight() }, false);
345         elem.appendChild(anchor);
346     },
347
348     _formatobject: function(obj, elem)
349     {
350         elem.appendChild(document.createTextNode(Object.describe(obj)));
351     },
352 }
353
354 WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype;
355
356 WebInspector.ConsoleMessage = function(source, level, message, line, url)
357 {
358     this.source = source;
359     this.level = level;
360     this.message = message;
361     this.line = line;
362     this.url = url;
363 }
364
365 WebInspector.ConsoleMessage.prototype = {
366     get shortURL()
367     {
368         if (this.resource)
369             return this.resource.displayName;
370         return this.url;
371     },
372
373     toMessageElement: function()
374     {
375         var element = document.createElement("div");
376         element.message = this;
377         element.className = "console-message";
378
379         switch (this.source) {
380             case WebInspector.ConsoleMessage.MessageSource.HTML:
381                 element.addStyleClass("console-html-source");
382                 break;
383             case WebInspector.ConsoleMessage.MessageSource.XML:
384                 element.addStyleClass("console-xml-source");
385                 break;
386             case WebInspector.ConsoleMessage.MessageSource.JS:
387                 element.addStyleClass("console-js-source");
388                 break;
389             case WebInspector.ConsoleMessage.MessageSource.CSS:
390                 element.addStyleClass("console-css-source");
391                 break;
392             case WebInspector.ConsoleMessage.MessageSource.Other:
393                 element.addStyleClass("console-other-source");
394                 break;
395         }
396
397         switch (this.level) {
398             case WebInspector.ConsoleMessage.MessageLevel.Tip:
399                 element.addStyleClass("console-tip-level");
400                 break;
401             case WebInspector.ConsoleMessage.MessageLevel.Log:
402                 element.addStyleClass("console-log-level");
403                 break;
404             case WebInspector.ConsoleMessage.MessageLevel.Warning:
405                 element.addStyleClass("console-warning-level");
406                 break;
407             case WebInspector.ConsoleMessage.MessageLevel.Error:
408                 element.addStyleClass("console-error-level");
409         }
410
411         var messageTextElement = document.createElement("span");
412         messageTextElement.className = "console-message-text";
413         messageTextElement.textContent = this.message;
414         element.appendChild(messageTextElement);
415
416         element.appendChild(document.createTextNode(" "));
417
418         if (this.url && this.url !== "undefined") {
419             var urlElement = document.createElement("a");
420             urlElement.className = "console-message-url";
421
422             if (this.line > 0)
423                 urlElement.textContent = WebInspector.UIString("%s (line %d)", this.url, this.line);
424             else
425                 urlElement.textContent = this.url;
426
427             element.appendChild(urlElement);
428         }
429
430         return element;
431     },
432
433     toString: function()
434     {
435         var sourceString;
436         switch (this.source) {
437             case WebInspector.ConsoleMessage.MessageSource.HTML:
438                 sourceString = "HTML";
439                 break;
440             case WebInspector.ConsoleMessage.MessageSource.XML:
441                 sourceString = "XML";
442                 break;
443             case WebInspector.ConsoleMessage.MessageSource.JS:
444                 sourceString = "JS";
445                 break;
446             case WebInspector.ConsoleMessage.MessageSource.CSS:
447                 sourceString = "CSS";
448                 break;
449             case WebInspector.ConsoleMessage.MessageSource.Other:
450                 sourceString = "Other";
451                 break;
452         }
453
454         var levelString;
455         switch (this.level) {
456             case WebInspector.ConsoleMessage.MessageLevel.Tip:
457                 levelString = "Tip";
458                 break;
459             case WebInspector.ConsoleMessage.MessageLevel.Log:
460                 levelString = "Log";
461                 break;
462             case WebInspector.ConsoleMessage.MessageLevel.Warning:
463                 levelString = "Warning";
464                 break;
465             case WebInspector.ConsoleMessage.MessageLevel.Error:
466                 levelString = "Error";
467                 break;
468         }
469
470         return sourceString + " " + levelString + ": " + this.message + "\n" + this.url + " line " + this.line;
471     }
472 }
473
474 // Note: Keep these constants in sync with the ones in Chrome.h
475 WebInspector.ConsoleMessage.MessageSource = {
476     HTML: 0,
477     XML: 1,
478     JS: 2,
479     CSS: 3,
480     Other: 4,
481 }
482
483 WebInspector.ConsoleMessage.MessageLevel = {
484     Tip: 0,
485     Log: 1,
486     Warning: 2,
487     Error: 3
488 }
489
490 WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level)
491 {
492     this.command = command;
493     this.formattedResultElement = formattedResultElement;
494     this.level = level;
495 }
496
497 WebInspector.ConsoleCommand.prototype = {
498     toMessageElement: function()
499     {
500         var element = document.createElement("div");
501         element.command = this;
502         element.className = "console-user-command";
503
504         var commandTextElement = document.createElement("span");
505         commandTextElement.className = "console-message-text";
506         commandTextElement.textContent = this.command;
507         element.appendChild(commandTextElement);
508
509         var resultElement = document.createElement("div");
510         resultElement.className = "console-message";
511         element.appendChild(resultElement);
512
513         switch (this.level) {
514             case WebInspector.ConsoleMessage.MessageLevel.Log:
515                 resultElement.addStyleClass("console-log-level");
516                 break;
517             case WebInspector.ConsoleMessage.MessageLevel.Warning:
518                 resultElement.addStyleClass("console-warning-level");
519                 break;
520             case WebInspector.ConsoleMessage.MessageLevel.Error:
521                 resultElement.addStyleClass("console-error-level");
522         }
523
524         var resultTextElement = document.createElement("span");
525         resultTextElement.className = "console-message-text";
526         resultTextElement.appendChild(this.formattedResultElement);
527         resultElement.appendChild(resultTextElement);
528
529         return element;
530     }
531 }