Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>";
     31 
     32 WebInspector.ConsoleView = function(drawer)
     33 {
     34     WebInspector.View.call(this, document.getElementById("console-view"));
     35 
     36     this.messages = [];
     37     this.drawer = drawer;
     38 
     39     this.clearButton = document.getElementById("clear-console-status-bar-item");
     40     this.clearButton.title = WebInspector.UIString("Clear console log.");
     41     this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false);
     42 
     43     this.messagesElement = document.getElementById("console-messages");
     44     this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false);
     45     this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
     46 
     47     this.promptElement = document.getElementById("console-prompt");
     48     this.promptElement.className = "source-code";
     49     this.promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
     50     this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), ExpressionStopCharacters + ".");
     51 
     52     this.topGroup = new WebInspector.ConsoleGroup(null, 0);
     53     this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
     54     this.groupLevel = 0;
     55     this.currentGroup = this.topGroup;
     56 
     57     this.toggleConsoleButton = document.getElementById("console-status-bar-item");
     58     this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
     59     this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
     60 
     61     // Will hold the list of filter elements
     62     this.filterBarElement = document.getElementById("console-filter");
     63 
     64     function createDividerElement() {
     65         var dividerElement = document.createElement("div");
     66         dividerElement.addStyleClass("divider");
     67         this.filterBarElement.appendChild(dividerElement);
     68     }
     69 
     70     var updateFilterHandler = this._updateFilter.bind(this);
     71     function createFilterElement(category) {
     72         var categoryElement = document.createElement("li");
     73         categoryElement.category = category;
     74         categoryElement.addStyleClass(categoryElement.category);
     75         categoryElement.addEventListener("click", updateFilterHandler, false);
     76 
     77         var label = category.toString();
     78         categoryElement.appendChild(document.createTextNode(label));
     79 
     80         this.filterBarElement.appendChild(categoryElement);
     81         return categoryElement;
     82     }
     83 
     84     this.allElement = createFilterElement.call(this, "All");
     85     createDividerElement.call(this);
     86     this.errorElement = createFilterElement.call(this, "Errors");
     87     this.warningElement = createFilterElement.call(this, "Warnings");
     88     this.logElement = createFilterElement.call(this, "Logs");
     89 
     90     this.filter(this.allElement, false);
     91 
     92     this._shortcuts = {};
     93 
     94     var shortcut;
     95     var clearConsoleHandler = this.requestClearMessages.bind(this);
     96 
     97     shortcut = WebInspector.KeyboardShortcut.makeKey("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
     98     this._shortcuts[shortcut] = clearConsoleHandler;
     99     this._shortcuts[shortcut].isMacOnly = true;
    100     shortcut = WebInspector.KeyboardShortcut.makeKey("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
    101     this._shortcuts[shortcut] = clearConsoleHandler;
    102 
    103     // Since the Context Menu for the Console View will always be the same, we can create it in
    104     // the constructor.
    105     this._contextMenu = new WebInspector.ContextMenu();
    106     this._contextMenu.appendItem(WebInspector.UIString("Clear Console"), clearConsoleHandler);
    107     this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
    108 
    109     this._customFormatters = {
    110         "object": this._formatobject,
    111         "array":  this._formatarray,
    112         "node":   this._formatnode,
    113         "string": this._formatstring
    114     };
    115 }
    116 
    117 WebInspector.ConsoleView.prototype = {
    118 
    119     _updateFilter: function(e)
    120     {
    121         var isMac = WebInspector.isMac();
    122         var selectMultiple = false;
    123         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
    124             selectMultiple = true;
    125         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
    126             selectMultiple = true;
    127 
    128         this.filter(e.target, selectMultiple);
    129     },
    130 
    131     filter: function(target, selectMultiple)
    132     {
    133         function unselectAll()
    134         {
    135             this.allElement.removeStyleClass("selected");
    136             this.errorElement.removeStyleClass("selected");
    137             this.warningElement.removeStyleClass("selected");
    138             this.logElement.removeStyleClass("selected");
    139 
    140             this.messagesElement.removeStyleClass("filter-all");
    141             this.messagesElement.removeStyleClass("filter-errors");
    142             this.messagesElement.removeStyleClass("filter-warnings");
    143             this.messagesElement.removeStyleClass("filter-logs");
    144         }
    145 
    146         var targetFilterClass = "filter-" + target.category.toLowerCase();
    147 
    148         if (target.category == "All") {
    149             if (target.hasStyleClass("selected")) {
    150                 // We can't unselect all, so we break early here
    151                 return;
    152             }
    153 
    154             unselectAll.call(this);
    155         } else {
    156             // Something other than all is being selected, so we want to unselect all
    157             if (this.allElement.hasStyleClass("selected")) {
    158                 this.allElement.removeStyleClass("selected");
    159                 this.messagesElement.removeStyleClass("filter-all");
    160             }
    161         }
    162 
    163         if (!selectMultiple) {
    164             // If multiple selection is off, we want to unselect everything else
    165             // and just select ourselves.
    166             unselectAll.call(this);
    167 
    168             target.addStyleClass("selected");
    169             this.messagesElement.addStyleClass(targetFilterClass);
    170 
    171             return;
    172         }
    173 
    174         if (target.hasStyleClass("selected")) {
    175             // If selectMultiple is turned on, and we were selected, we just
    176             // want to unselect ourselves.
    177             target.removeStyleClass("selected");
    178             this.messagesElement.removeStyleClass(targetFilterClass);
    179         } else {
    180             // If selectMultiple is turned on, and we weren't selected, we just
    181             // want to select ourselves.
    182             target.addStyleClass("selected");
    183             this.messagesElement.addStyleClass(targetFilterClass);
    184         }
    185     },
    186 
    187     _toggleConsoleButtonClicked: function()
    188     {
    189         this.drawer.visibleView = this;
    190     },
    191 
    192     attach: function(mainElement, statusBarElement)
    193     {
    194         mainElement.appendChild(this.element);
    195         statusBarElement.appendChild(this.clearButton);
    196         statusBarElement.appendChild(this.filterBarElement);
    197     },
    198 
    199     show: function()
    200     {
    201         this.toggleConsoleButton.addStyleClass("toggled-on");
    202         this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
    203         if (!this.prompt.isCaretInsidePrompt())
    204             this.prompt.moveCaretToEndOfPrompt();
    205     },
    206 
    207     afterShow: function()
    208     {
    209         WebInspector.currentFocusElement = this.promptElement;
    210     },
    211 
    212     hide: function()
    213     {
    214         this.toggleConsoleButton.removeStyleClass("toggled-on");
    215         this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    216     },
    217 
    218     addMessage: function(msg)
    219     {
    220         if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
    221             this._incrementErrorWarningCount(msg);
    222 
    223             // Add message to the resource panel
    224             if (msg.url in WebInspector.resourceURLMap) {
    225                 msg.resource = WebInspector.resourceURLMap[msg.url];
    226                 if (WebInspector.panels.resources)
    227                     WebInspector.panels.resources.addMessageToResource(msg.resource, msg);
    228             }
    229 
    230             this.commandSincePreviousMessage = false;
    231             this.previousMessage = msg;
    232         } else if (msg instanceof WebInspector.ConsoleCommand) {
    233             if (this.previousMessage) {
    234                 this.commandSincePreviousMessage = true;
    235             }
    236         }
    237 
    238         this.messages.push(msg);
    239 
    240         if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
    241             if (this.groupLevel < 1)
    242                 return;
    243 
    244             this.groupLevel--;
    245 
    246             this.currentGroup = this.currentGroup.parentGroup;
    247         } else {
    248             if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
    249                 this.groupLevel++;
    250 
    251                 var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel);
    252                 this.currentGroup.messagesElement.appendChild(group.element);
    253                 this.currentGroup = group;
    254             }
    255 
    256             this.currentGroup.addMessage(msg);
    257         }
    258 
    259         this.promptElement.scrollIntoView(false);
    260     },
    261 
    262     updateMessageRepeatCount: function(count)
    263     {
    264         var msg = this.previousMessage;
    265         var prevRepeatCount = msg.totalRepeatCount;
    266 
    267         if (!this.commandSincePreviousMessage) {
    268             msg.repeatDelta = count - prevRepeatCount;
    269             msg.repeatCount = msg.repeatCount + msg.repeatDelta;
    270             msg.totalRepeatCount = count;
    271             msg._updateRepeatCount();
    272             this._incrementErrorWarningCount(msg);
    273         } else {
    274             msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, msg.groupLevel, count - prevRepeatCount);
    275             msgCopy.totalRepeatCount = count;
    276             msgCopy.setMessageBody(msg.args);
    277             this.addMessage(msgCopy);
    278         }
    279     },
    280 
    281     _incrementErrorWarningCount: function(msg)
    282     {
    283         switch (msg.level) {
    284             case WebInspector.ConsoleMessage.MessageLevel.Warning:
    285                 WebInspector.warnings += msg.repeatDelta;
    286                 break;
    287             case WebInspector.ConsoleMessage.MessageLevel.Error:
    288                 WebInspector.errors += msg.repeatDelta;
    289                 break;
    290         }
    291     },
    292 
    293     requestClearMessages: function()
    294     {
    295         InjectedScriptAccess.getDefault().clearConsoleMessages(function() {});
    296     },
    297 
    298     clearMessages: function()
    299     {
    300         if (WebInspector.panels.resources)
    301             WebInspector.panels.resources.clearMessages();
    302 
    303         this.messages = [];
    304 
    305         this.groupLevel = 0;
    306         this.currentGroup = this.topGroup;
    307         this.topGroup.messagesElement.removeChildren();
    308 
    309         WebInspector.errors = 0;
    310         WebInspector.warnings = 0;
    311 
    312         delete this.commandSincePreviousMessage;
    313         delete this.previousMessage;
    314     },
    315 
    316     completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    317     {
    318         // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
    319         var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward");
    320         var expressionString = expressionRange.toString();
    321         var lastIndex = expressionString.length - 1;
    322 
    323         var dotNotation = (expressionString[lastIndex] === ".");
    324         var bracketNotation = (expressionString[lastIndex] === "[");
    325 
    326         if (dotNotation || bracketNotation)
    327             expressionString = expressionString.substr(0, lastIndex);
    328 
    329         var prefix = wordRange.toString();
    330         if (!expressionString && !prefix)
    331             return;
    332 
    333         var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix);
    334         // Collect comma separated object properties for the completion.
    335 
    336         var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation);
    337         var callFrameId = WebInspector.panels.scripts.selectedCallFrameId();
    338         var injectedScriptAccess;
    339         if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
    340             var selectedCallFrame = WebInspector.panels.scripts.sidebarPanes.callstack.selectedCallFrame;
    341             injectedScriptAccess = InjectedScriptAccess.get(selectedCallFrame.injectedScriptId);
    342         } else
    343             injectedScriptAccess = InjectedScriptAccess.getDefault();
    344         injectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, callFrameId, reportCompletions);
    345     },
    346 
    347     _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
    348         if (isException)
    349             return;
    350 
    351         if (bracketNotation) {
    352             if (prefix.length && prefix[0] === "'")
    353                 var quoteUsed = "'";
    354             else
    355                 var quoteUsed = "\"";
    356         }
    357 
    358         var results = [];
    359         var properties = Object.sortedProperties(result);
    360 
    361         for (var i = 0; i < properties.length; ++i) {
    362             var property = properties[i];
    363 
    364             if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
    365                 continue;
    366 
    367             if (bracketNotation) {
    368                 if (!/^[0-9]+$/.test(property))
    369                     property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
    370                 property += "]";
    371             }
    372 
    373             if (property.length < prefix.length)
    374                 continue;
    375             if (property.indexOf(prefix) !== 0)
    376                 continue;
    377 
    378             results.push(property);
    379             if (bestMatchOnly)
    380                 break;
    381         }
    382         completionsReadyCallback(results);
    383     },
    384 
    385     _clearButtonClicked: function()
    386     {
    387         this.requestClearMessages();
    388     },
    389 
    390     _handleContextMenuEvent: function(event)
    391     {
    392         if (!window.getSelection().isCollapsed) {
    393             // If there is a selection, we want to show our normal context menu
    394             // (with Copy, etc.), and not Clear Console.
    395             return;
    396         }
    397 
    398         this._contextMenu.show(event);
    399     },
    400 
    401     _messagesSelectStart: function(event)
    402     {
    403         if (this._selectionTimeout)
    404             clearTimeout(this._selectionTimeout);
    405 
    406         this.prompt.clearAutoComplete();
    407 
    408         function moveBackIfOutside()
    409         {
    410             delete this._selectionTimeout;
    411             if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
    412                 this.prompt.moveCaretToEndOfPrompt();
    413             this.prompt.autoCompleteSoon();
    414         }
    415 
    416         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    417     },
    418 
    419     _messagesClicked: function(event)
    420     {
    421         var link = event.target.enclosingNodeOrSelfWithNodeName("a");
    422         if (!link || !link.representedNode)
    423             return;
    424 
    425         WebInspector.updateFocusedNode(link.representedNode.id);
    426         event.stopPropagation();
    427         event.preventDefault();
    428     },
    429 
    430     _promptKeyDown: function(event)
    431     {
    432         if (isEnterKey(event)) {
    433             this._enterKeyPressed(event);
    434             return;
    435         }
    436 
    437         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
    438         var handler = this._shortcuts[shortcut];
    439         if (handler) {
    440             if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
    441                 handler();
    442                 event.preventDefault();
    443                 return;
    444             }
    445         }
    446     },
    447 
    448     evalInInspectedWindow: function(expression, objectGroup, callback)
    449     {
    450         if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
    451             WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, objectGroup, callback);
    452             return;
    453         }
    454         this.doEvalInWindow(expression, objectGroup, callback);
    455     },
    456 
    457     doEvalInWindow: function(expression, objectGroup, callback)
    458     {
    459         if (!expression) {
    460             // There is no expression, so the completion should happen against global properties.
    461             expression = "this";
    462         }
    463 
    464         function evalCallback(result)
    465         {
    466             callback(result.value, result.isException);
    467         };
    468         InjectedScriptAccess.getDefault().evaluate(expression, objectGroup, evalCallback);
    469     },
    470 
    471     _enterKeyPressed: function(event)
    472     {
    473         if (event.altKey)
    474             return;
    475 
    476         event.preventDefault();
    477         event.stopPropagation();
    478 
    479         this.prompt.clearAutoComplete(true);
    480 
    481         var str = this.prompt.text;
    482         if (!str.length)
    483             return;
    484 
    485         var commandMessage = new WebInspector.ConsoleCommand(str);
    486         this.addMessage(commandMessage);
    487 
    488         var self = this;
    489         function printResult(result, exception)
    490         {
    491             self.prompt.history.push(str);
    492             self.prompt.historyOffset = 0;
    493             self.prompt.text = "";
    494             self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage));
    495         }
    496         this.evalInInspectedWindow(str, "console", printResult);
    497     },
    498 
    499     _format: function(output, forceObjectFormat)
    500     {
    501         var isProxy = (output != null && typeof output === "object");
    502         var type = (forceObjectFormat ? "object" : Object.proxyType(output));
    503 
    504         var formatter = this._customFormatters[type];
    505         if (!formatter || !isProxy) {
    506             formatter = this._formatvalue;
    507             output = output.description || output;
    508         }
    509 
    510         var span = document.createElement("span");
    511         span.className = "console-formatted-" + type + " source-code";
    512         formatter.call(this, output, span);
    513         return span;
    514     },
    515 
    516     _formatvalue: function(val, elem)
    517     {
    518         elem.appendChild(document.createTextNode(val));
    519     },
    520 
    521     _formatobject: function(obj, elem)
    522     {
    523         elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
    524     },
    525 
    526     _formatnode: function(object, elem)
    527     {
    528         function printNode(nodeId)
    529         {
    530             if (!nodeId)
    531                 return;
    532             var treeOutline = new WebInspector.ElementsTreeOutline();
    533             treeOutline.showInElementsPanelEnabled = true;
    534             treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
    535             treeOutline.element.addStyleClass("outline-disclosure");
    536             if (!treeOutline.children[0].hasChildren)
    537                 treeOutline.element.addStyleClass("single-node");
    538             elem.appendChild(treeOutline.element);
    539         }
    540 
    541         InjectedScriptAccess.get(object.injectedScriptId).pushNodeToFrontend(object, printNode);
    542     },
    543 
    544     _formatarray: function(arr, elem)
    545     {
    546         InjectedScriptAccess.get(arr.injectedScriptId).getProperties(arr, false, false, this._printArray.bind(this, elem));
    547     },
    548 
    549     _formatstring: function(output, elem)
    550     {
    551         var span = document.createElement("span");
    552         span.className = "console-formatted-string source-code";
    553         span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
    554 
    555         // Make black quotes.
    556         elem.removeStyleClass("console-formatted-string");
    557         elem.appendChild(document.createTextNode("\""));
    558         elem.appendChild(span);
    559         elem.appendChild(document.createTextNode("\""));
    560     },
    561 
    562     _printArray: function(elem, properties)
    563     {
    564         if (!properties)
    565             return;
    566 
    567         var elements = [];
    568         for (var i = 0; i < properties.length; ++i) {
    569             var name = properties[i].name;
    570             if (name == parseInt(name))
    571                 elements[name] = this._format(properties[i].value);
    572         }
    573 
    574         elem.appendChild(document.createTextNode("["));
    575         for (var i = 0; i < elements.length; ++i) {
    576             var element = elements[i];
    577             if (element)
    578                 elem.appendChild(element);
    579             else
    580                 elem.appendChild(document.createTextNode("undefined"))
    581             if (i < elements.length - 1)
    582                 elem.appendChild(document.createTextNode(", "));
    583         }
    584         elem.appendChild(document.createTextNode("]"));
    585     }
    586 }
    587 
    588 WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
    589 
    590 WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount)
    591 {
    592     this.source = source;
    593     this.type = type;
    594     this.level = level;
    595     this.line = line;
    596     this.url = url;
    597     this.groupLevel = groupLevel;
    598     this.repeatCount = repeatCount;
    599     this.repeatDelta = repeatCount;
    600     this.totalRepeatCount = repeatCount;
    601     if (arguments.length > 7)
    602         this.setMessageBody(Array.prototype.slice.call(arguments, 7));
    603 }
    604 
    605 WebInspector.ConsoleMessage.prototype = {
    606     setMessageBody: function(args)
    607     {
    608         this.args = args;
    609         switch (this.type) {
    610             case WebInspector.ConsoleMessage.MessageType.Trace:
    611                 var span = document.createElement("span");
    612                 span.className = "console-formatted-trace source-code";
    613                 var stack = Array.prototype.slice.call(args);
    614                 var funcNames = stack.map(function(f) {
    615                     return f || WebInspector.UIString("(anonymous function)");
    616                 });
    617                 span.appendChild(document.createTextNode(funcNames.join("\n")));
    618                 this.formattedMessage = span;
    619                 break;
    620             case WebInspector.ConsoleMessage.MessageType.Object:
    621                 this.formattedMessage = this._format(["%O", args[0]]);
    622                 break;
    623             default:
    624                 this.formattedMessage = this._format(args);
    625                 break;
    626         }
    627 
    628         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
    629         this.message = this.formattedMessage.textContent;
    630     },
    631 
    632     isErrorOrWarning: function()
    633     {
    634         return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
    635     },
    636 
    637     _format: function(parameters)
    638     {
    639         // This node is used like a Builder. Values are continually appended onto it.
    640         var formattedResult = document.createElement("span");
    641         if (!parameters.length)
    642             return formattedResult;
    643 
    644         // Formatting code below assumes that parameters are all wrappers whereas frontend console
    645         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
    646         for (var i = 0; i < parameters.length; ++i)
    647             if (typeof parameters[i] !== "object" && typeof parameters[i] !== "function")
    648                 parameters[i] = WebInspector.ObjectProxy.wrapPrimitiveValue(parameters[i]);
    649 
    650         // There can be string log and string eval result. We distinguish between them based on message type.
    651         var shouldFormatMessage = Object.proxyType(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
    652 
    653         // Multiple parameters with the first being a format string. Save unused substitutions.
    654         if (shouldFormatMessage) {
    655             // Multiple parameters with the first being a format string. Save unused substitutions.
    656             var result = this._formatWithSubstitutionString(parameters, formattedResult);
    657             parameters = result.unusedSubstitutions;
    658             if (parameters.length)
    659                 formattedResult.appendChild(document.createTextNode(" "));
    660         }
    661 
    662         // Single parameter, or unused substitutions from above.
    663         for (var i = 0; i < parameters.length; ++i) {
    664             // Inline strings when formatting.
    665             if (shouldFormatMessage && parameters[i].type === "string")
    666                 formattedResult.appendChild(document.createTextNode(parameters[i].description));
    667             else
    668                 formattedResult.appendChild(WebInspector.console._format(parameters[i]));
    669             if (i < parameters.length - 1)
    670                 formattedResult.appendChild(document.createTextNode(" "));
    671         }
    672         return formattedResult;
    673     },
    674 
    675     _formatWithSubstitutionString: function(parameters, formattedResult)
    676     {
    677         var formatters = {}
    678         for (var i in String.standardFormatters)
    679             formatters[i] = String.standardFormatters[i];
    680 
    681         function consoleFormatWrapper(force)
    682         {
    683             return function(obj) {
    684                 return WebInspector.console._format(obj, force);
    685             };
    686         }
    687 
    688         // Firebug uses %o for formatting objects.
    689         formatters.o = consoleFormatWrapper();
    690         // Firebug allows both %i and %d for formatting integers.
    691         formatters.i = formatters.d;
    692         // Support %O to force object formatting, instead of the type-based %o formatting.
    693         formatters.O = consoleFormatWrapper(true);
    694 
    695         function append(a, b)
    696         {
    697             if (!(b instanceof Node))
    698                 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
    699             else
    700                 a.appendChild(b);
    701             return a;
    702         }
    703 
    704         // String.format does treat formattedResult like a Builder, result is an object.
    705         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
    706     },
    707 
    708     toMessageElement: function()
    709     {
    710         if (this._element)
    711             return this._element;
    712 
    713         var element = document.createElement("div");
    714         element.message = this;
    715         element.className = "console-message";
    716 
    717         this._element = element;
    718 
    719         switch (this.source) {
    720             case WebInspector.ConsoleMessage.MessageSource.HTML:
    721                 element.addStyleClass("console-html-source");
    722                 break;
    723             case WebInspector.ConsoleMessage.MessageSource.WML:
    724                 element.addStyleClass("console-wml-source");
    725                 break;
    726             case WebInspector.ConsoleMessage.MessageSource.XML:
    727                 element.addStyleClass("console-xml-source");
    728                 break;
    729             case WebInspector.ConsoleMessage.MessageSource.JS:
    730                 element.addStyleClass("console-js-source");
    731                 break;
    732             case WebInspector.ConsoleMessage.MessageSource.CSS:
    733                 element.addStyleClass("console-css-source");
    734                 break;
    735             case WebInspector.ConsoleMessage.MessageSource.Other:
    736                 element.addStyleClass("console-other-source");
    737                 break;
    738         }
    739 
    740         switch (this.level) {
    741             case WebInspector.ConsoleMessage.MessageLevel.Tip:
    742                 element.addStyleClass("console-tip-level");
    743                 break;
    744             case WebInspector.ConsoleMessage.MessageLevel.Log:
    745                 element.addStyleClass("console-log-level");
    746                 break;
    747             case WebInspector.ConsoleMessage.MessageLevel.Debug:
    748                 element.addStyleClass("console-debug-level");
    749                 break;
    750             case WebInspector.ConsoleMessage.MessageLevel.Warning:
    751                 element.addStyleClass("console-warning-level");
    752                 break;
    753             case WebInspector.ConsoleMessage.MessageLevel.Error:
    754                 element.addStyleClass("console-error-level");
    755                 break;
    756         }
    757 
    758         if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup)
    759             element.addStyleClass("console-group-title");
    760 
    761         if (this.elementsTreeOutline) {
    762             element.addStyleClass("outline-disclosure");
    763             element.appendChild(this.elementsTreeOutline.element);
    764             return element;
    765         }
    766 
    767         if (this.url && this.url !== "undefined") {
    768             var urlElement = document.createElement("a");
    769             urlElement.className = "console-message-url webkit-html-resource-link";
    770             urlElement.href = this.url;
    771             urlElement.lineNumber = this.line;
    772 
    773             if (this.source === WebInspector.ConsoleMessage.MessageSource.JS)
    774                 urlElement.preferredPanel = "scripts";
    775 
    776             if (this.line > 0)
    777                 urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line;
    778             else
    779                 urlElement.textContent = WebInspector.displayNameForURL(this.url);
    780 
    781             element.appendChild(urlElement);
    782         }
    783 
    784         var messageTextElement = document.createElement("span");
    785         messageTextElement.className = "console-message-text source-code";
    786         if (this.type === WebInspector.ConsoleMessage.MessageType.Assert)
    787             messageTextElement.appendChild(document.createTextNode(WebInspector.UIString("Assertion failed: ")));
    788         messageTextElement.appendChild(this.formattedMessage);
    789         element.appendChild(messageTextElement);
    790 
    791         if (this.repeatCount > 1)
    792             this._updateRepeatCount();
    793 
    794         return element;
    795     },
    796 
    797     _updateRepeatCount: function() {
    798         if (!this.repeatCountElement) {
    799             this.repeatCountElement = document.createElement("span");
    800             this.repeatCountElement.className = "bubble";
    801 
    802             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
    803             this._element.addStyleClass("repeated-message");
    804         }
    805         this.repeatCountElement.textContent = this.repeatCount;
    806     },
    807 
    808     toString: function()
    809     {
    810         var sourceString;
    811         switch (this.source) {
    812             case WebInspector.ConsoleMessage.MessageSource.HTML:
    813                 sourceString = "HTML";
    814                 break;
    815             case WebInspector.ConsoleMessage.MessageSource.WML:
    816                 sourceString = "WML";
    817                 break;
    818             case WebInspector.ConsoleMessage.MessageSource.XML:
    819                 sourceString = "XML";
    820                 break;
    821             case WebInspector.ConsoleMessage.MessageSource.JS:
    822                 sourceString = "JS";
    823                 break;
    824             case WebInspector.ConsoleMessage.MessageSource.CSS:
    825                 sourceString = "CSS";
    826                 break;
    827             case WebInspector.ConsoleMessage.MessageSource.Other:
    828                 sourceString = "Other";
    829                 break;
    830         }
    831 
    832         var typeString;
    833         switch (this.type) {
    834             case WebInspector.ConsoleMessage.MessageType.Log:
    835                 typeString = "Log";
    836                 break;
    837             case WebInspector.ConsoleMessage.MessageType.Object:
    838                 typeString = "Object";
    839                 break;
    840             case WebInspector.ConsoleMessage.MessageType.Trace:
    841                 typeString = "Trace";
    842                 break;
    843             case WebInspector.ConsoleMessage.MessageType.StartGroup:
    844                 typeString = "Start Group";
    845                 break;
    846             case WebInspector.ConsoleMessage.MessageType.EndGroup:
    847                 typeString = "End Group";
    848                 break;
    849             case WebInspector.ConsoleMessage.MessageType.Assert:
    850                 typeString = "Assert";
    851                 break;
    852             case WebInspector.ConsoleMessage.MessageType.Result:
    853                 typeString = "Result";
    854                 break;
    855         }
    856 
    857         var levelString;
    858         switch (this.level) {
    859             case WebInspector.ConsoleMessage.MessageLevel.Tip:
    860                 levelString = "Tip";
    861                 break;
    862             case WebInspector.ConsoleMessage.MessageLevel.Log:
    863                 levelString = "Log";
    864                 break;
    865             case WebInspector.ConsoleMessage.MessageLevel.Warning:
    866                 levelString = "Warning";
    867                 break;
    868             case WebInspector.ConsoleMessage.MessageLevel.Debug:
    869                 levelString = "Debug";
    870                 break;
    871             case WebInspector.ConsoleMessage.MessageLevel.Error:
    872                 levelString = "Error";
    873                 break;
    874         }
    875 
    876         return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line;
    877     },
    878 
    879     isEqual: function(msg, disreguardGroup)
    880     {
    881         if (!msg)
    882             return false;
    883 
    884         var ret = (this.source == msg.source)
    885             && (this.type == msg.type)
    886             && (this.level == msg.level)
    887             && (this.line == msg.line)
    888             && (this.url == msg.url)
    889             && (this.message == msg.message);
    890 
    891         return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel)));
    892     }
    893 }
    894 
    895 // Note: Keep these constants in sync with the ones in Console.h
    896 WebInspector.ConsoleMessage.MessageSource = {
    897     HTML: 0,
    898     WML: 1,
    899     XML: 2,
    900     JS: 3,
    901     CSS: 4,
    902     Other: 5
    903 }
    904 
    905 WebInspector.ConsoleMessage.MessageType = {
    906     Log: 0,
    907     Object: 1,
    908     Trace: 2,
    909     StartGroup: 3,
    910     EndGroup: 4,
    911     Assert: 5,
    912     Result: 6
    913 }
    914 
    915 WebInspector.ConsoleMessage.MessageLevel = {
    916     Tip: 0,
    917     Log: 1,
    918     Warning: 2,
    919     Error: 3,
    920     Debug: 4
    921 }
    922 
    923 WebInspector.ConsoleCommand = function(command)
    924 {
    925     this.command = command;
    926 }
    927 
    928 WebInspector.ConsoleCommand.prototype = {
    929     toMessageElement: function()
    930     {
    931         var element = document.createElement("div");
    932         element.command = this;
    933         element.className = "console-user-command";
    934 
    935         var commandTextElement = document.createElement("span");
    936         commandTextElement.className = "console-message-text source-code";
    937         commandTextElement.textContent = this.command;
    938         element.appendChild(commandTextElement);
    939 
    940         return element;
    941     }
    942 }
    943 
    944 WebInspector.ConsoleTextMessage = function(text, level)
    945 {
    946     level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
    947     WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text);
    948 }
    949 
    950 WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
    951 
    952 WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand)
    953 {
    954     var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
    955     var message = result;
    956     if (exception) {
    957         // Distinguish between strings and errors (no need to quote latter).
    958         message = WebInspector.ObjectProxy.wrapPrimitiveValue(result);
    959         message.type = "error";
    960     }
    961     var line = (exception ? result.line : -1);
    962     var url = (exception ? result.sourceURL : null);
    963 
    964     this.originatingCommand = originatingCommand;
    965 
    966     WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, line, url, null, 1, message);
    967 }
    968 
    969 WebInspector.ConsoleCommandResult.prototype = {
    970     toMessageElement: function()
    971     {
    972         var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
    973         element.addStyleClass("console-user-command-result");
    974         return element;
    975     }
    976 }
    977 
    978 WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
    979 
    980 WebInspector.ConsoleGroup = function(parentGroup, level)
    981 {
    982     this.parentGroup = parentGroup;
    983     this.level = level;
    984 
    985     var element = document.createElement("div");
    986     element.className = "console-group";
    987     element.group = this;
    988     this.element = element;
    989 
    990     var messagesElement = document.createElement("div");
    991     messagesElement.className = "console-group-messages";
    992     element.appendChild(messagesElement);
    993     this.messagesElement = messagesElement;
    994 }
    995 
    996 WebInspector.ConsoleGroup.prototype = {
    997     addMessage: function(msg)
    998     {
    999         var element = msg.toMessageElement();
   1000 
   1001         if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) {
   1002             this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
   1003             element.addEventListener("click", this._titleClicked.bind(this), true);
   1004         } else
   1005             this.messagesElement.appendChild(element);
   1006 
   1007         if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
   1008             element.previousSibling.addStyleClass("console-adjacent-user-command-result");
   1009     },
   1010 
   1011     _titleClicked: function(event)
   1012     {
   1013         var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
   1014         if (groupTitleElement) {
   1015             var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
   1016             if (groupElement)
   1017                 if (groupElement.hasStyleClass("collapsed"))
   1018                     groupElement.removeStyleClass("collapsed");
   1019                 else
   1020                     groupElement.addStyleClass("collapsed");
   1021             groupTitleElement.scrollIntoViewIfNeeded(true);
   1022         }
   1023 
   1024         event.stopPropagation();
   1025         event.preventDefault();
   1026     }
   1027 }
   1028