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     this.prompt.history = WebInspector.settings.consoleHistory;
     52 
     53     this.topGroup = new WebInspector.ConsoleGroup(null);
     54     this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
     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("scope-bar-divider");
     67         this.filterBarElement.appendChild(dividerElement);
     68     }
     69 
     70     var updateFilterHandler = this._updateFilter.bind(this);
     71     function createFilterElement(category, label) {
     72         var categoryElement = document.createElement("li");
     73         categoryElement.category = category;
     74         categoryElement.className = category;
     75         categoryElement.addEventListener("click", updateFilterHandler, false);
     76         categoryElement.textContent = label;
     77 
     78         this.filterBarElement.appendChild(categoryElement);
     79 
     80         return categoryElement;
     81     }
     82 
     83     this.allElement = createFilterElement.call(this, "all", WebInspector.UIString("All"));
     84     createDividerElement.call(this);
     85     this.errorElement = createFilterElement.call(this, "errors", WebInspector.UIString("Errors"));
     86     this.warningElement = createFilterElement.call(this, "warnings", WebInspector.UIString("Warnings"));
     87     this.logElement = createFilterElement.call(this, "logs", WebInspector.UIString("Logs"));
     88 
     89     this.filter(this.allElement, false);
     90     this._registerShortcuts();
     91 
     92     this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
     93 
     94     this._customFormatters = {
     95         "object": this._formatobject,
     96         "array":  this._formatarray,
     97         "node":   this._formatnode,
     98         "string": this._formatstring
     99     };
    100 
    101     this._registerConsoleDomainDispatcher();
    102 }
    103 
    104 WebInspector.ConsoleView.prototype = {
    105     _registerConsoleDomainDispatcher: function() {
    106         var console = this;
    107         var dispatcher = {
    108             messageAdded: function(payload)
    109             {
    110                 var consoleMessage = new WebInspector.ConsoleMessage(
    111                     payload.source,
    112                     payload.type,
    113                     payload.level,
    114                     payload.line,
    115                     payload.url,
    116                     payload.repeatCount,
    117                     payload.text,
    118                     payload.parameters,
    119                     payload.stackTrace,
    120                     payload.networkIdentifier);
    121                 console.addMessage(consoleMessage);
    122             },
    123 
    124             messageRepeatCountUpdated: function(count)
    125             {
    126                 var msg = console.previousMessage;
    127                 var prevRepeatCount = msg.totalRepeatCount;
    128 
    129                 if (!console.commandSincePreviousMessage) {
    130                     msg.repeatDelta = count - prevRepeatCount;
    131                     msg.repeatCount = msg.repeatCount + msg.repeatDelta;
    132                     msg.totalRepeatCount = count;
    133                     msg._updateRepeatCount();
    134                     console._incrementErrorWarningCount(msg);
    135                 } else {
    136                     var msgCopy = new WebInspector.ConsoleMessage(msg.source, msg.type, msg.level, msg.line, msg.url, count - prevRepeatCount, msg._messageText, msg._parameters, msg._stackTrace, msg._requestId);
    137                     msgCopy.totalRepeatCount = count;
    138                     msgCopy._formatMessage();
    139                     console.addMessage(msgCopy);
    140                 }
    141             },
    142 
    143             messagesCleared: function()
    144             {
    145                 console.clearMessages();
    146             },
    147         }
    148         InspectorBackend.registerDomainDispatcher("Console", dispatcher);
    149     },
    150 
    151     setConsoleMessageExpiredCount: function(count)
    152     {
    153         if (count) {
    154             var message = String.sprintf(WebInspector.UIString("%d console messages are not shown."), count);
    155             this.addMessage(WebInspector.ConsoleMessage.createTextMessage(message, WebInspector.ConsoleMessage.MessageLevel.Warning));
    156         }
    157     },
    158 
    159     _updateFilter: function(e)
    160     {
    161         var isMac = WebInspector.isMac();
    162         var selectMultiple = false;
    163         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
    164             selectMultiple = true;
    165         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
    166             selectMultiple = true;
    167 
    168         this.filter(e.target, selectMultiple);
    169     },
    170 
    171     filter: function(target, selectMultiple)
    172     {
    173         function unselectAll()
    174         {
    175             this.allElement.removeStyleClass("selected");
    176             this.errorElement.removeStyleClass("selected");
    177             this.warningElement.removeStyleClass("selected");
    178             this.logElement.removeStyleClass("selected");
    179 
    180             this.messagesElement.removeStyleClass("filter-all");
    181             this.messagesElement.removeStyleClass("filter-errors");
    182             this.messagesElement.removeStyleClass("filter-warnings");
    183             this.messagesElement.removeStyleClass("filter-logs");
    184         }
    185 
    186         var targetFilterClass = "filter-" + target.category;
    187 
    188         if (target.category === "all") {
    189             if (target.hasStyleClass("selected")) {
    190                 // We can't unselect all, so we break early here
    191                 return;
    192             }
    193 
    194             unselectAll.call(this);
    195         } else {
    196             // Something other than all is being selected, so we want to unselect all
    197             if (this.allElement.hasStyleClass("selected")) {
    198                 this.allElement.removeStyleClass("selected");
    199                 this.messagesElement.removeStyleClass("filter-all");
    200             }
    201         }
    202 
    203         if (!selectMultiple) {
    204             // If multiple selection is off, we want to unselect everything else
    205             // and just select ourselves.
    206             unselectAll.call(this);
    207 
    208             target.addStyleClass("selected");
    209             this.messagesElement.addStyleClass(targetFilterClass);
    210 
    211             return;
    212         }
    213 
    214         if (target.hasStyleClass("selected")) {
    215             // If selectMultiple is turned on, and we were selected, we just
    216             // want to unselect ourselves.
    217             target.removeStyleClass("selected");
    218             this.messagesElement.removeStyleClass(targetFilterClass);
    219         } else {
    220             // If selectMultiple is turned on, and we weren't selected, we just
    221             // want to select ourselves.
    222             target.addStyleClass("selected");
    223             this.messagesElement.addStyleClass(targetFilterClass);
    224         }
    225     },
    226 
    227     _toggleConsoleButtonClicked: function()
    228     {
    229         this.drawer.visibleView = this;
    230     },
    231 
    232     attach: function(mainElement, statusBarElement)
    233     {
    234         mainElement.appendChild(this.element);
    235         statusBarElement.appendChild(this.clearButton);
    236         statusBarElement.appendChild(this.filterBarElement);
    237     },
    238 
    239     show: function()
    240     {
    241         this.toggleConsoleButton.addStyleClass("toggled-on");
    242         this.toggleConsoleButton.title = WebInspector.UIString("Hide console.");
    243         if (!this.prompt.isCaretInsidePrompt())
    244             this.prompt.moveCaretToEndOfPrompt();
    245     },
    246 
    247     afterShow: function()
    248     {
    249         WebInspector.currentFocusElement = this.promptElement;
    250     },
    251 
    252     hide: function()
    253     {
    254         this.toggleConsoleButton.removeStyleClass("toggled-on");
    255         this.toggleConsoleButton.title = WebInspector.UIString("Show console.");
    256     },
    257 
    258     _scheduleScrollIntoView: function()
    259     {
    260         if (this._scrollIntoViewTimer)
    261             return;
    262 
    263         function scrollIntoView()
    264         {
    265             this.promptElement.scrollIntoView(true);
    266             delete this._scrollIntoViewTimer;
    267         }
    268         this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
    269     },
    270 
    271     addMessage: function(msg)
    272     {
    273         var shouldScrollToLastMessage = this.messagesElement.isScrolledToBottom();
    274 
    275         if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) {
    276             this._incrementErrorWarningCount(msg);
    277             WebInspector.resourceTreeModel.addConsoleMessage(msg);
    278             WebInspector.panels.scripts.addConsoleMessage(msg);
    279             this.commandSincePreviousMessage = false;
    280             this.previousMessage = msg;
    281         } else if (msg instanceof WebInspector.ConsoleCommand) {
    282             if (this.previousMessage) {
    283                 this.commandSincePreviousMessage = true;
    284             }
    285         }
    286 
    287         this.messages.push(msg);
    288 
    289         if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
    290             var parentGroup = this.currentGroup.parentGroup
    291             if (parentGroup)
    292                 this.currentGroup = parentGroup;
    293         } else {
    294             if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
    295                 var group = new WebInspector.ConsoleGroup(this.currentGroup);
    296                 this.currentGroup.messagesElement.appendChild(group.element);
    297                 this.currentGroup = group;
    298             }
    299 
    300             this.currentGroup.addMessage(msg);
    301         }
    302 
    303         // Always scroll when command result arrives.
    304         if (shouldScrollToLastMessage || (msg instanceof WebInspector.ConsoleCommandResult))
    305             this._scheduleScrollIntoView();
    306     },
    307 
    308     _incrementErrorWarningCount: function(msg)
    309     {
    310         switch (msg.level) {
    311             case WebInspector.ConsoleMessage.MessageLevel.Warning:
    312                 WebInspector.warnings += msg.repeatDelta;
    313                 break;
    314             case WebInspector.ConsoleMessage.MessageLevel.Error:
    315                 WebInspector.errors += msg.repeatDelta;
    316                 break;
    317         }
    318     },
    319 
    320     requestClearMessages: function()
    321     {
    322         ConsoleAgent.clearConsoleMessages();
    323     },
    324 
    325     clearMessages: function()
    326     {
    327         WebInspector.resourceTreeModel.clearConsoleMessages();
    328         WebInspector.panels.scripts.clearConsoleMessages();
    329 
    330         this.messages = [];
    331 
    332         this.currentGroup = this.topGroup;
    333         this.topGroup.messagesElement.removeChildren();
    334 
    335         WebInspector.errors = 0;
    336         WebInspector.warnings = 0;
    337 
    338         delete this.commandSincePreviousMessage;
    339         delete this.previousMessage;
    340     },
    341 
    342     completions: function(wordRange, bestMatchOnly, completionsReadyCallback)
    343     {
    344         // Pass less stop characters to rangeOfWord so the range will be a more complete expression.
    345         var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward");
    346         var expressionString = expressionRange.toString();
    347         var prefix = wordRange.toString();
    348         this._completions(expressionString, prefix, bestMatchOnly, completionsReadyCallback);
    349     },
    350 
    351     _completions: function(expressionString, prefix, bestMatchOnly, completionsReadyCallback)
    352     {
    353         var lastIndex = expressionString.length - 1;
    354 
    355         var dotNotation = (expressionString[lastIndex] === ".");
    356         var bracketNotation = (expressionString[lastIndex] === "[");
    357 
    358         if (dotNotation || bracketNotation)
    359             expressionString = expressionString.substr(0, lastIndex);
    360 
    361         if (!expressionString && !prefix)
    362             return;
    363 
    364         if (!expressionString && WebInspector.panels.scripts.paused)
    365             WebInspector.panels.scripts.getSelectedCallFrameVariables(reportCompletions.bind(this));
    366         else
    367             this.evalInInspectedWindow(expressionString, "completion", true, evaluated.bind(this));
    368 
    369         function evaluated(result)
    370         {
    371             if (!result)
    372                 return;
    373             result.getAllProperties(evaluatedProperties.bind(this));
    374         }
    375 
    376         function evaluatedProperties(properties)
    377         {
    378             RuntimeAgent.releaseObjectGroup("completion");
    379             var propertyNames = {};
    380             for (var i = 0; properties && i < properties.length; ++i)
    381                 propertyNames[properties[i].name] = true;
    382             reportCompletions.call(this, propertyNames);
    383         }
    384 
    385         function reportCompletions(propertyNames)
    386         {
    387             var includeCommandLineAPI = (!dotNotation && !bracketNotation);
    388             if (includeCommandLineAPI) {
    389                 const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear"];
    390                 for (var i = 0; i < commandLineAPI.length; ++i)
    391                     propertyNames[commandLineAPI[i]] = true;
    392             }
    393 
    394             this._reportCompletions(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, Object.keys(propertyNames));
    395         }
    396     },
    397 
    398     _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, properties) {
    399         if (bracketNotation) {
    400             if (prefix.length && prefix[0] === "'")
    401                 var quoteUsed = "'";
    402             else
    403                 var quoteUsed = "\"";
    404         }
    405 
    406         var results = [];
    407         properties.sort();
    408 
    409         for (var i = 0; i < properties.length; ++i) {
    410             var property = properties[i];
    411 
    412             if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
    413                 continue;
    414 
    415             if (bracketNotation) {
    416                 if (!/^[0-9]+$/.test(property))
    417                     property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed;
    418                 property += "]";
    419             }
    420 
    421             if (property.length < prefix.length)
    422                 continue;
    423             if (property.indexOf(prefix) !== 0)
    424                 continue;
    425 
    426             results.push(property);
    427             if (bestMatchOnly)
    428                 break;
    429         }
    430         completionsReadyCallback(results);
    431     },
    432 
    433     _clearButtonClicked: function()
    434     {
    435         this.requestClearMessages();
    436     },
    437 
    438     _handleContextMenuEvent: function(event)
    439     {
    440         if (!window.getSelection().isCollapsed) {
    441             // If there is a selection, we want to show our normal context menu
    442             // (with Copy, etc.), and not Clear Console.
    443             return;
    444         }
    445 
    446         var itemAction = function () {
    447             WebInspector.settings.monitoringXHREnabled = !WebInspector.settings.monitoringXHREnabled;
    448             ConsoleAgent.setMonitoringXHREnabled(WebInspector.settings.monitoringXHREnabled);
    449         }.bind(this);
    450         var contextMenu = new WebInspector.ContextMenu();
    451         contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.settings.monitoringXHREnabled)
    452         contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this));
    453         contextMenu.show(event);
    454     },
    455 
    456     _messagesSelectStart: function(event)
    457     {
    458         if (this._selectionTimeout)
    459             clearTimeout(this._selectionTimeout);
    460 
    461         this.prompt.clearAutoComplete();
    462 
    463         function moveBackIfOutside()
    464         {
    465             delete this._selectionTimeout;
    466             if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
    467                 this.prompt.moveCaretToEndOfPrompt();
    468             this.prompt.autoCompleteSoon();
    469         }
    470 
    471         this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
    472     },
    473 
    474     _messagesClicked: function(event)
    475     {
    476         var link = event.target.enclosingNodeOrSelfWithNodeName("a");
    477         if (!link || !link.representedNode)
    478             return;
    479 
    480         WebInspector.updateFocusedNode(link.representedNode.id);
    481         event.stopPropagation();
    482         event.preventDefault();
    483     },
    484 
    485     _registerShortcuts: function()
    486     {
    487         this._shortcuts = {};
    488 
    489         var shortcut = WebInspector.KeyboardShortcut;
    490         var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
    491         // This case requires a separate bound function as its isMacOnly property should not be shared among different shortcut handlers.
    492         this._shortcuts[shortcutK.key] = this.requestClearMessages.bind(this);
    493         this._shortcuts[shortcutK.key].isMacOnly = true;
    494 
    495         var clearConsoleHandler = this.requestClearMessages.bind(this);
    496         var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
    497         this._shortcuts[shortcutL.key] = clearConsoleHandler;
    498 
    499         var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
    500         var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ];
    501         section.addAlternateKeys(keys, WebInspector.UIString("Clear Console"));
    502 
    503         keys = [
    504             shortcut.shortcutToString(shortcut.Keys.Tab),
    505             shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift)
    506         ];
    507         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion"));
    508         section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
    509         keys = [
    510             shortcut.shortcutToString(shortcut.Keys.Down),
    511             shortcut.shortcutToString(shortcut.Keys.Up)
    512         ];
    513         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
    514         keys = [
    515             shortcut.shortcutToString("N", shortcut.Modifiers.Alt),
    516             shortcut.shortcutToString("P", shortcut.Modifiers.Alt)
    517         ];
    518         if (WebInspector.isMac())
    519             section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
    520         section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
    521     },
    522 
    523     _promptKeyDown: function(event)
    524     {
    525         if (isEnterKey(event)) {
    526             this._enterKeyPressed(event);
    527             return;
    528         }
    529 
    530         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
    531         var handler = this._shortcuts[shortcut];
    532         if (handler) {
    533             if (!this._shortcuts[shortcut].isMacOnly || WebInspector.isMac()) {
    534                 handler();
    535                 event.preventDefault();
    536                 return;
    537             }
    538         }
    539     },
    540 
    541     evalInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, callback)
    542     {
    543         if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) {
    544             WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, callback);
    545             return;
    546         }
    547 
    548         if (!expression) {
    549             // There is no expression, so the completion should happen against global properties.
    550             expression = "this";
    551         }
    552 
    553         function evalCallback(error, result)
    554         {
    555             if (!error)
    556                 callback(WebInspector.RemoteObject.fromPayload(result));
    557         }
    558         RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, evalCallback);
    559     },
    560 
    561     _enterKeyPressed: function(event)
    562     {
    563         if (event.altKey || event.ctrlKey || event.shiftKey)
    564             return;
    565 
    566         event.preventDefault();
    567         event.stopPropagation();
    568 
    569         this.prompt.clearAutoComplete(true);
    570 
    571         var str = this.prompt.text;
    572         if (!str.length)
    573             return;
    574 
    575         var commandMessage = new WebInspector.ConsoleCommand(str);
    576         this.addMessage(commandMessage);
    577 
    578         var self = this;
    579         function printResult(result)
    580         {
    581             self.prompt.history.push(str);
    582             self.prompt.historyOffset = 0;
    583             self.prompt.text = "";
    584 
    585             WebInspector.settings.consoleHistory = self.prompt.history.slice(-30);
    586 
    587             self.addMessage(new WebInspector.ConsoleCommandResult(result, commandMessage));
    588         }
    589         this.evalInInspectedWindow(str, "console", true, printResult);
    590     },
    591 
    592     _format: function(output, forceObjectFormat)
    593     {
    594         var isProxy = (output != null && typeof output === "object");
    595         var type = (forceObjectFormat ? "object" : WebInspector.RemoteObject.type(output));
    596 
    597         var formatter = this._customFormatters[type];
    598         if (!formatter || !isProxy) {
    599             formatter = this._formatvalue;
    600             output = output.description;
    601         }
    602 
    603         var span = document.createElement("span");
    604         span.className = "console-formatted-" + type + " source-code";
    605         formatter.call(this, output, span);
    606         return span;
    607     },
    608 
    609     _formatvalue: function(val, elem)
    610     {
    611         elem.appendChild(document.createTextNode(val));
    612     },
    613 
    614     _formatobject: function(obj, elem)
    615     {
    616         elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element);
    617     },
    618 
    619     _formatnode: function(object, elem)
    620     {
    621         function printNode(nodeId)
    622         {
    623             if (!nodeId) {
    624                 // Sometimes DOM is loaded after the sync message is being formatted, so we get no
    625                 // nodeId here. So we fall back to object formatting here.
    626                 this._formatobject(object, elem);
    627                 return;
    628             }
    629             var treeOutline = new WebInspector.ElementsTreeOutline();
    630             treeOutline.showInElementsPanelEnabled = true;
    631             treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId);
    632             treeOutline.element.addStyleClass("outline-disclosure");
    633             if (!treeOutline.children[0].hasChildren)
    634                 treeOutline.element.addStyleClass("single-node");
    635             elem.appendChild(treeOutline.element);
    636         }
    637         object.pushNodeToFrontend(printNode.bind(this));
    638     },
    639 
    640     _formatarray: function(arr, elem)
    641     {
    642         arr.getOwnProperties(this._printArray.bind(this, elem));
    643     },
    644 
    645     _formatstring: function(output, elem)
    646     {
    647         var span = document.createElement("span");
    648         span.className = "console-formatted-string source-code";
    649         span.appendChild(WebInspector.linkifyStringAsFragment(output.description));
    650 
    651         // Make black quotes.
    652         elem.removeStyleClass("console-formatted-string");
    653         elem.appendChild(document.createTextNode("\""));
    654         elem.appendChild(span);
    655         elem.appendChild(document.createTextNode("\""));
    656     },
    657 
    658     _printArray: function(elem, properties)
    659     {
    660         if (!properties)
    661             return;
    662 
    663         var elements = [];
    664         for (var i = 0; i < properties.length; ++i) {
    665             var name = properties[i].name;
    666             if (name == parseInt(name))
    667                 elements[name] = this._formatAsArrayEntry(properties[i].value);
    668         }
    669 
    670         elem.appendChild(document.createTextNode("["));
    671         for (var i = 0; i < elements.length; ++i) {
    672             var element = elements[i];
    673             if (element)
    674                 elem.appendChild(element);
    675             else
    676                 elem.appendChild(document.createTextNode("undefined"))
    677             if (i < elements.length - 1)
    678                 elem.appendChild(document.createTextNode(", "));
    679         }
    680         elem.appendChild(document.createTextNode("]"));
    681     },
    682 
    683     _formatAsArrayEntry: function(output)
    684     {
    685         // Prevent infinite expansion of cross-referencing arrays.
    686         return this._format(output, WebInspector.RemoteObject.type(output) === "array");
    687     }
    688 }
    689 
    690 WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype;
    691 
    692 WebInspector.ConsoleMessage = function(source, type, level, line, url, repeatCount, message, parameters, stackTrace, requestId)
    693 {
    694     this.source = source;
    695     this.type = type;
    696     this.level = level;
    697     this.line = line;
    698     this.url = url;
    699     this.repeatCount = repeatCount;
    700     this.repeatDelta = repeatCount;
    701     this.totalRepeatCount = repeatCount;
    702     this._messageText = message;
    703     this._parameters = parameters;
    704     this._stackTrace = stackTrace;
    705     this._requestId = requestId;
    706 
    707     if (stackTrace && stackTrace.length) {
    708         var topCallFrame = stackTrace[0];
    709         if (!this.url)
    710             this.url = topCallFrame.url;
    711         if (!this.line)
    712             this.line = topCallFrame.lineNumber;
    713     }
    714 
    715     this._formatMessage();
    716 }
    717 
    718 WebInspector.ConsoleMessage.createTextMessage = function(text, level)
    719 {
    720     level = level || WebInspector.ConsoleMessage.MessageLevel.Log;
    721     return new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, 1, null, [text], null);
    722 }
    723 
    724 WebInspector.ConsoleMessage.prototype = {
    725     _formatMessage: function()
    726     {
    727         var stackTrace = this._stackTrace;
    728         var messageText;
    729         switch (this.type) {
    730             case WebInspector.ConsoleMessage.MessageType.Trace:
    731                 messageText = document.createTextNode("console.trace()");
    732                 break;
    733             case WebInspector.ConsoleMessage.MessageType.UncaughtException:
    734                 messageText = document.createTextNode(this._messageText);
    735                 break;
    736             case WebInspector.ConsoleMessage.MessageType.NetworkError:
    737                 var resource = this._requestId && WebInspector.networkResourceById(this._requestId);
    738                 if (resource) {
    739                     stackTrace = resource.stackTrace;
    740 
    741                     messageText = document.createElement("span");
    742                     messageText.appendChild(document.createTextNode(resource.requestMethod + " "));
    743                     messageText.appendChild(WebInspector.linkifyURLAsNode(resource.url));
    744                     if (resource.failed)
    745                         messageText.appendChild(document.createTextNode(" " + resource.localizedFailDescription));
    746                     else
    747                         messageText.appendChild(document.createTextNode(" " + resource.statusCode + " (" + resource.statusText + ")"));
    748                 } else
    749                     messageText = this._format([this._messageText]);
    750                 break;
    751             case WebInspector.ConsoleMessage.MessageType.Assert:
    752                 var args = [WebInspector.UIString("Assertion failed:")];
    753                 if (this._parameters)
    754                     args = args.concat(this._parameters);
    755                 messageText = this._format(args);
    756                 break;
    757             case WebInspector.ConsoleMessage.MessageType.Object:
    758                 var obj = this._parameters ? this._parameters[0] : undefined;
    759                 var args = ["%O", obj];
    760                 messageText = this._format(args);
    761                 break;
    762             default:
    763                 var args = this._parameters || [this._messageText];
    764                 messageText = this._format(args);
    765                 break;
    766         }
    767 
    768         this._formattedMessage = document.createElement("span");
    769         this._formattedMessage.className = "console-message-text source-code";
    770 
    771         if (this.url && this.url !== "undefined") {
    772             var urlElement = WebInspector.linkifyResourceAsNode(this.url, "scripts", this.line, "console-message-url");
    773             this._formattedMessage.appendChild(urlElement);
    774         }
    775 
    776         this._formattedMessage.appendChild(messageText);
    777 
    778         if (this._stackTrace) {
    779             switch (this.type) {
    780                 case WebInspector.ConsoleMessage.MessageType.Trace:
    781                 case WebInspector.ConsoleMessage.MessageType.UncaughtException:
    782                 case WebInspector.ConsoleMessage.MessageType.NetworkError:
    783                 case WebInspector.ConsoleMessage.MessageType.Assert: {
    784                     var ol = document.createElement("ol");
    785                     ol.className = "outline-disclosure";
    786                     var treeOutline = new TreeOutline(ol);
    787 
    788                     var content = this._formattedMessage;
    789                     var root = new TreeElement(content, null, true);
    790                     content.treeElementForTest = root;
    791                     treeOutline.appendChild(root);
    792                     if (this.type === WebInspector.ConsoleMessage.MessageType.Trace)
    793                         root.expand();
    794 
    795                     this._populateStackTraceTreeElement(root);
    796                     this._formattedMessage = ol;
    797                 }
    798             }
    799         }
    800 
    801         // This is used for inline message bubbles in SourceFrames, or other plain-text representations.
    802         this.message = this._formattedMessage.textContent;
    803     },
    804 
    805     isErrorOrWarning: function()
    806     {
    807         return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error);
    808     },
    809 
    810     _format: function(parameters)
    811     {
    812         // This node is used like a Builder. Values are continually appended onto it.
    813         var formattedResult = document.createElement("span");
    814         if (!parameters.length)
    815             return formattedResult;
    816 
    817         // Formatting code below assumes that parameters are all wrappers whereas frontend console
    818         // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here.
    819         for (var i = 0; i < parameters.length; ++i) {
    820             if (typeof parameters[i] === "object")
    821                 parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]);
    822             else
    823                 parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]);
    824         }
    825 
    826         // There can be string log and string eval result. We distinguish between them based on message type.
    827         var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result;
    828 
    829         // Multiple parameters with the first being a format string. Save unused substitutions.
    830         if (shouldFormatMessage) {
    831             // Multiple parameters with the first being a format string. Save unused substitutions.
    832             var result = this._formatWithSubstitutionString(parameters, formattedResult);
    833             parameters = result.unusedSubstitutions;
    834             if (parameters.length)
    835                 formattedResult.appendChild(document.createTextNode(" "));
    836         }
    837 
    838         // Single parameter, or unused substitutions from above.
    839         for (var i = 0; i < parameters.length; ++i) {
    840             // Inline strings when formatting.
    841             if (shouldFormatMessage && parameters[i].type === "string")
    842                 formattedResult.appendChild(document.createTextNode(parameters[i].description));
    843             else
    844                 formattedResult.appendChild(WebInspector.console._format(parameters[i]));
    845             if (i < parameters.length - 1)
    846                 formattedResult.appendChild(document.createTextNode(" "));
    847         }
    848         return formattedResult;
    849     },
    850 
    851     _formatWithSubstitutionString: function(parameters, formattedResult)
    852     {
    853         var formatters = {}
    854         for (var i in String.standardFormatters)
    855             formatters[i] = String.standardFormatters[i];
    856 
    857         function consoleFormatWrapper(force)
    858         {
    859             return function(obj) {
    860                 return WebInspector.console._format(obj, force);
    861             };
    862         }
    863 
    864         // Firebug uses %o for formatting objects.
    865         formatters.o = consoleFormatWrapper();
    866         // Firebug allows both %i and %d for formatting integers.
    867         formatters.i = formatters.d;
    868         // Support %O to force object formatting, instead of the type-based %o formatting.
    869         formatters.O = consoleFormatWrapper(true);
    870 
    871         function append(a, b)
    872         {
    873             if (!(b instanceof Node))
    874                 a.appendChild(WebInspector.linkifyStringAsFragment(b.toString()));
    875             else
    876                 a.appendChild(b);
    877             return a;
    878         }
    879 
    880         // String.format does treat formattedResult like a Builder, result is an object.
    881         return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append);
    882     },
    883 
    884     toMessageElement: function()
    885     {
    886         if (this._element)
    887             return this._element;
    888 
    889         var element = document.createElement("div");
    890         element.message = this;
    891         element.className = "console-message";
    892 
    893         this._element = element;
    894 
    895         switch (this.level) {
    896             case WebInspector.ConsoleMessage.MessageLevel.Tip:
    897                 element.addStyleClass("console-tip-level");
    898                 break;
    899             case WebInspector.ConsoleMessage.MessageLevel.Log:
    900                 element.addStyleClass("console-log-level");
    901                 break;
    902             case WebInspector.ConsoleMessage.MessageLevel.Debug:
    903                 element.addStyleClass("console-debug-level");
    904                 break;
    905             case WebInspector.ConsoleMessage.MessageLevel.Warning:
    906                 element.addStyleClass("console-warning-level");
    907                 break;
    908             case WebInspector.ConsoleMessage.MessageLevel.Error:
    909                 element.addStyleClass("console-error-level");
    910                 break;
    911         }
    912 
    913         if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
    914             element.addStyleClass("console-group-title");
    915 
    916         if (this.elementsTreeOutline) {
    917             element.addStyleClass("outline-disclosure");
    918             element.appendChild(this.elementsTreeOutline.element);
    919             return element;
    920         }
    921 
    922         element.appendChild(this._formattedMessage);
    923 
    924         if (this.repeatCount > 1)
    925             this._updateRepeatCount();
    926 
    927         return element;
    928     },
    929 
    930     _populateStackTraceTreeElement: function(parentTreeElement)
    931     {
    932         for (var i = 0; i < this._stackTrace.length; i++) {
    933             var frame = this._stackTrace[i];
    934 
    935             var content = document.createElement("div");
    936             var messageTextElement = document.createElement("span");
    937             messageTextElement.className = "console-message-text source-code";
    938             var functionName = frame.functionName || WebInspector.UIString("(anonymous function)");
    939             messageTextElement.appendChild(document.createTextNode(functionName));
    940             content.appendChild(messageTextElement);
    941 
    942             var urlElement = WebInspector.linkifyResourceAsNode(frame.url, "scripts", frame.lineNumber, "console-message-url");
    943             content.appendChild(urlElement);
    944 
    945             var treeElement = new TreeElement(content);
    946             parentTreeElement.appendChild(treeElement);
    947         }
    948     },
    949 
    950     _updateRepeatCount: function() {
    951         if (!this.repeatCountElement) {
    952             this.repeatCountElement = document.createElement("span");
    953             this.repeatCountElement.className = "bubble";
    954 
    955             this._element.insertBefore(this.repeatCountElement, this._element.firstChild);
    956             this._element.addStyleClass("repeated-message");
    957         }
    958         this.repeatCountElement.textContent = this.repeatCount;
    959     },
    960 
    961     toString: function()
    962     {
    963         var sourceString;
    964         switch (this.source) {
    965             case WebInspector.ConsoleMessage.MessageSource.HTML:
    966                 sourceString = "HTML";
    967                 break;
    968             case WebInspector.ConsoleMessage.MessageSource.WML:
    969                 sourceString = "WML";
    970                 break;
    971             case WebInspector.ConsoleMessage.MessageSource.XML:
    972                 sourceString = "XML";
    973                 break;
    974             case WebInspector.ConsoleMessage.MessageSource.JS:
    975                 sourceString = "JS";
    976                 break;
    977             case WebInspector.ConsoleMessage.MessageSource.CSS:
    978                 sourceString = "CSS";
    979                 break;
    980             case WebInspector.ConsoleMessage.MessageSource.Other:
    981                 sourceString = "Other";
    982                 break;
    983         }
    984 
    985         var typeString;
    986         switch (this.type) {
    987             case WebInspector.ConsoleMessage.MessageType.Log:
    988             case WebInspector.ConsoleMessage.MessageType.UncaughtException:
    989             case WebInspector.ConsoleMessage.MessageType.NetworkError:
    990                 typeString = "Log";
    991                 break;
    992             case WebInspector.ConsoleMessage.MessageType.Object:
    993                 typeString = "Object";
    994                 break;
    995             case WebInspector.ConsoleMessage.MessageType.Trace:
    996                 typeString = "Trace";
    997                 break;
    998             case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
    999             case WebInspector.ConsoleMessage.MessageType.StartGroup:
   1000                 typeString = "Start Group";
   1001                 break;
   1002             case WebInspector.ConsoleMessage.MessageType.EndGroup:
   1003                 typeString = "End Group";
   1004                 break;
   1005             case WebInspector.ConsoleMessage.MessageType.Assert:
   1006                 typeString = "Assert";
   1007                 break;
   1008             case WebInspector.ConsoleMessage.MessageType.Result:
   1009                 typeString = "Result";
   1010                 break;
   1011         }
   1012 
   1013         var levelString;
   1014         switch (this.level) {
   1015             case WebInspector.ConsoleMessage.MessageLevel.Tip:
   1016                 levelString = "Tip";
   1017                 break;
   1018             case WebInspector.ConsoleMessage.MessageLevel.Log:
   1019                 levelString = "Log";
   1020                 break;
   1021             case WebInspector.ConsoleMessage.MessageLevel.Warning:
   1022                 levelString = "Warning";
   1023                 break;
   1024             case WebInspector.ConsoleMessage.MessageLevel.Debug:
   1025                 levelString = "Debug";
   1026                 break;
   1027             case WebInspector.ConsoleMessage.MessageLevel.Error:
   1028                 levelString = "Error";
   1029                 break;
   1030         }
   1031 
   1032         return sourceString + " " + typeString + " " + levelString + ": " + this._formattedMessage.textContent + "\n" + this.url + " line " + this.line;
   1033     },
   1034 
   1035     isEqual: function(msg)
   1036     {
   1037         if (!msg)
   1038             return false;
   1039 
   1040         if (this._stackTrace) {
   1041             if (!msg._stackTrace)
   1042                 return false;
   1043             var l = this._stackTrace;
   1044             var r = msg._stackTrace;
   1045             for (var i = 0; i < l.length; i++) {
   1046                 if (l[i].url !== r[i].url ||
   1047                     l[i].functionName !== r[i].functionName ||
   1048                     l[i].lineNumber !== r[i].lineNumber ||
   1049                     l[i].columnNumber !== r[i].columnNumber)
   1050                     return false;
   1051             }
   1052         }
   1053 
   1054         return (this.source === msg.source)
   1055             && (this.type === msg.type)
   1056             && (this.level === msg.level)
   1057             && (this.line === msg.line)
   1058             && (this.url === msg.url)
   1059             && (this.message === msg.message)
   1060             && (this._requestId === msg._requestId);
   1061     }
   1062 }
   1063 
   1064 // Note: Keep these constants in sync with the ones in Console.h
   1065 WebInspector.ConsoleMessage.MessageSource = {
   1066     HTML: "html",
   1067     WML: "wml",
   1068     XML: "xml",
   1069     JS: "javascript",
   1070     CSS: "css",
   1071     Other: "other"
   1072 }
   1073 
   1074 WebInspector.ConsoleMessage.MessageType = {
   1075     Log: "log",
   1076     Object: "other",
   1077     Trace: "trace",
   1078     StartGroup: "startGroup",
   1079     StartGroupCollapsed: "startGroupCollapsed",
   1080     EndGroup: "endGroup",
   1081     Assert: "assert",
   1082     UncaughtException: "uncaughtException",
   1083     NetworkError: "networkError",
   1084     Result: "result"
   1085 }
   1086 
   1087 WebInspector.ConsoleMessage.MessageLevel = {
   1088     Tip: "tip",
   1089     Log: "log",
   1090     Warning: "warning",
   1091     Error: "error",
   1092     Debug: "debug"
   1093 }
   1094 
   1095 WebInspector.ConsoleCommand = function(command)
   1096 {
   1097     this.command = command;
   1098 }
   1099 
   1100 WebInspector.ConsoleCommand.prototype = {
   1101     toMessageElement: function()
   1102     {
   1103         var element = document.createElement("div");
   1104         element.command = this;
   1105         element.className = "console-user-command";
   1106 
   1107         var commandTextElement = document.createElement("span");
   1108         commandTextElement.className = "console-message-text source-code";
   1109         commandTextElement.textContent = this.command;
   1110         element.appendChild(commandTextElement);
   1111 
   1112         return element;
   1113     }
   1114 }
   1115 
   1116 WebInspector.ConsoleCommandResult = function(result, originatingCommand)
   1117 {
   1118     var level = (result.isError() ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
   1119     this.originatingCommand = originatingCommand;
   1120     WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Result, level, -1, null, 1, null, [result]);
   1121 }
   1122 
   1123 WebInspector.ConsoleCommandResult.prototype = {
   1124     toMessageElement: function()
   1125     {
   1126         var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this);
   1127         element.addStyleClass("console-user-command-result");
   1128         return element;
   1129     }
   1130 }
   1131 
   1132 WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype;
   1133 
   1134 WebInspector.ConsoleGroup = function(parentGroup)
   1135 {
   1136     this.parentGroup = parentGroup;
   1137 
   1138     var element = document.createElement("div");
   1139     element.className = "console-group";
   1140     element.group = this;
   1141     this.element = element;
   1142 
   1143     var messagesElement = document.createElement("div");
   1144     messagesElement.className = "console-group-messages";
   1145     element.appendChild(messagesElement);
   1146     this.messagesElement = messagesElement;
   1147 }
   1148 
   1149 WebInspector.ConsoleGroup.prototype = {
   1150     addMessage: function(msg)
   1151     {
   1152         var element = msg.toMessageElement();
   1153 
   1154         if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
   1155             this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
   1156             element.addEventListener("click", this._titleClicked.bind(this), false);
   1157             var groupElement = element.enclosingNodeOrSelfWithClass("console-group");
   1158             if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
   1159                 groupElement.addStyleClass("collapsed");
   1160         } else
   1161             this.messagesElement.appendChild(element);
   1162 
   1163         if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand)
   1164             element.previousSibling.addStyleClass("console-adjacent-user-command-result");
   1165     },
   1166 
   1167     _titleClicked: function(event)
   1168     {
   1169         var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
   1170         if (groupTitleElement) {
   1171             var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
   1172             if (groupElement)
   1173                 if (groupElement.hasStyleClass("collapsed"))
   1174                     groupElement.removeStyleClass("collapsed");
   1175                 else
   1176                     groupElement.addStyleClass("collapsed");
   1177             groupTitleElement.scrollIntoViewIfNeeded(true);
   1178         }
   1179 
   1180         event.stopPropagation();
   1181         event.preventDefault();
   1182     }
   1183 }
   1184 
   1185