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 /**
     31  * @extends {WebInspector.View}
     32  * @implements {WebInspector.Searchable}
     33  * @constructor
     34  * @param {boolean} hideContextSelector
     35  */
     36 WebInspector.ConsoleView = function(hideContextSelector)
     37 {
     38     WebInspector.View.call(this);
     39 
     40     this.element.id = "console-view";
     41     this._visibleMessagesIndices = [];
     42     this._urlToMessageCount = {};
     43 
     44     this._clearConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear console log."), "clear-status-bar-item");
     45     this._clearConsoleButton.addEventListener("click", this._requestClearMessages, this);
     46 
     47     this._frameSelector = new WebInspector.StatusBarComboBox(this._frameChanged.bind(this), "console-context");
     48     this._contextSelector = new WebInspector.StatusBarComboBox(this._contextChanged.bind(this), "console-context");
     49 
     50     this._filter = new WebInspector.ConsoleViewFilter();
     51     this._filter.addEventListener(WebInspector.ConsoleViewFilter.Events.FilterChanged, this._updateMessageList.bind(this));
     52 
     53     if (hideContextSelector) {
     54         this._frameSelector.element.addStyleClass("hidden");
     55         this._contextSelector.element.addStyleClass("hidden");
     56     }
     57 
     58     this.messagesElement = document.createElement("div");
     59     this.messagesElement.id = "console-messages";
     60     this.messagesElement.className = "monospace";
     61     this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
     62     this.element.appendChild(this.messagesElement);
     63     this._scrolledToBottom = true;
     64 
     65     this.promptElement = document.createElement("div");
     66     this.promptElement.id = "console-prompt";
     67     this.promptElement.className = "source-code";
     68     this.promptElement.spellcheck = false;
     69     this.messagesElement.appendChild(this.promptElement);
     70     this.messagesElement.appendChild(document.createElement("br"));
     71 
     72     this.topGroup = new WebInspector.ConsoleGroup(null);
     73     this.messagesElement.insertBefore(this.topGroup.element, this.promptElement);
     74     this.currentGroup = this.topGroup;
     75 
     76     this._registerShortcuts();
     77     this.registerRequiredCSS("textPrompt.css");
     78 
     79     this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
     80 
     81     WebInspector.settings.monitoringXHREnabled.addChangeListener(this._monitoringXHREnabledSettingChanged.bind(this));
     82 
     83     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this);
     84     WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
     85 
     86     this._linkifier = new WebInspector.Linkifier();
     87 
     88     this.prompt = new WebInspector.TextPromptWithHistory(WebInspector.runtimeModel.completionsForTextPrompt.bind(WebInspector.runtimeModel));
     89     this.prompt.setSuggestBoxEnabled("generic-suggest");
     90     this.prompt.renderAsBlock();
     91     this.prompt.attach(this.promptElement);
     92     this.prompt.proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false);
     93     this.prompt.setHistoryData(WebInspector.settings.consoleHistory.get());
     94 
     95     WebInspector.runtimeModel.contextLists().forEach(this._addFrame, this);
     96     WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListAdded, this._frameAdded, this);
     97     WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.FrameExecutionContextListRemoved, this._frameRemoved, this);
     98 
     99     this._filterStatusMessageElement = document.createElement("div");
    100     this._filterStatusMessageElement.classList.add("console-message");
    101     this._filterStatusTextElement = this._filterStatusMessageElement.createChild("span", "console-info");
    102     this._filterStatusMessageElement.createTextChild(" ");
    103     var resetFiltersLink = this._filterStatusMessageElement.createChild("span", "console-info node-link");
    104     resetFiltersLink.textContent = WebInspector.UIString("Show all messages.");
    105     resetFiltersLink.addEventListener("click", this._filter.reset.bind(this._filter), true);
    106 
    107     this.messagesElement.insertBefore(this._filterStatusMessageElement, this.topGroup.element);
    108 
    109     this._updateFilterStatus();
    110 }
    111 
    112 WebInspector.ConsoleView.prototype = {
    113     get statusBarItems()
    114     {
    115         return [this._clearConsoleButton.element, this._frameSelector.element, this._contextSelector.element, this._filter.sourceFilterButton.element, this._filter.filterBarElement];
    116     },
    117 
    118     /**
    119      * @param {WebInspector.Event} event
    120      */
    121     _frameAdded: function(event)
    122     {
    123         var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
    124         this._addFrame(contextList);
    125     },
    126 
    127     /**
    128      * @param {WebInspector.FrameExecutionContextList} contextList
    129      */
    130     _addFrame: function(contextList)
    131     {
    132         var option = this._frameSelector.createOption(contextList.displayName, contextList.url);
    133         option._contextList = contextList;
    134         contextList._consoleOption = option;
    135         contextList.addEventListener(WebInspector.FrameExecutionContextList.EventTypes.ContextsUpdated, this._frameUpdated, this);
    136         contextList.addEventListener(WebInspector.FrameExecutionContextList.EventTypes.ContextAdded, this._contextAdded, this);
    137         this._frameChanged();
    138     },
    139 
    140     /**
    141      * @param {WebInspector.Event} event
    142      */
    143     _frameRemoved: function(event)
    144     {
    145         var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
    146         this._frameSelector.removeOption(contextList._consoleOption);
    147         this._frameChanged();
    148     },
    149 
    150     _frameChanged: function()
    151     {
    152         var context = this._currentFrame();
    153         if (!context) {
    154             WebInspector.runtimeModel.setCurrentExecutionContext(null);
    155             this._contextSelector.element.addStyleClass("hidden");
    156             return;
    157         }
    158 
    159         var executionContexts = context.executionContexts();
    160         if (executionContexts.length)
    161             WebInspector.runtimeModel.setCurrentExecutionContext(executionContexts[0]);
    162 
    163         if (executionContexts.length === 1) {
    164             this._contextSelector.element.addStyleClass("hidden");
    165             return;
    166         }
    167         this._contextSelector.element.removeStyleClass("hidden");
    168         this._contextSelector.removeOptions();
    169         for (var i = 0; i < executionContexts.length; ++i)
    170             this._appendContextOption(executionContexts[i]);
    171     },
    172 
    173     /**
    174      * @param {WebInspector.ExecutionContext} executionContext
    175      */
    176     _appendContextOption: function(executionContext)
    177     {
    178         if (!WebInspector.runtimeModel.currentExecutionContext())
    179             WebInspector.runtimeModel.setCurrentExecutionContext(executionContext);
    180         var option = this._contextSelector.createOption(executionContext.name, executionContext.id);
    181         option._executionContext = executionContext;
    182     },
    183 
    184     /**
    185      * @param {Event} event
    186      */
    187     _contextChanged: function(event)
    188     {
    189         var option = this._contextSelector.selectedOption();
    190         WebInspector.runtimeModel.setCurrentExecutionContext(option ? option._executionContext : null);
    191     },
    192 
    193     /**
    194      * @param {WebInspector.Event} event
    195      */
    196     _frameUpdated: function(event)
    197     {
    198         var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
    199         var option = contextList._consoleOption;
    200         option.text = contextList.displayName;
    201         option.title = contextList.url;
    202     },
    203 
    204     /**
    205      * @param {WebInspector.Event} event
    206      */
    207     _contextAdded: function(event)
    208     {
    209         var contextList = /** @type {WebInspector.FrameExecutionContextList} */ (event.data);
    210         if (contextList === this._currentFrame())
    211             this._frameChanged();
    212     },
    213 
    214     /**
    215      * @return {WebInspector.FrameExecutionContextList|undefined}
    216      */
    217     _currentFrame: function()
    218     {
    219         var option = this._frameSelector.selectedOption();
    220         return option ? option._contextList : undefined;
    221     },
    222 
    223     willHide: function()
    224     {
    225         this.prompt.hideSuggestBox();
    226         this.prompt.clearAutoComplete(true);
    227     },
    228 
    229     wasShown: function()
    230     {
    231         if (!this.prompt.isCaretInsidePrompt())
    232             this.prompt.moveCaretToEndOfPrompt();
    233     },
    234 
    235     afterShow: function()
    236     {
    237         WebInspector.setCurrentFocusElement(this.promptElement);
    238     },
    239 
    240     storeScrollPositions: function()
    241     {
    242         WebInspector.View.prototype.storeScrollPositions.call(this);
    243         this._scrolledToBottom = this.messagesElement.isScrolledToBottom();
    244     },
    245 
    246     restoreScrollPositions: function()
    247     {
    248         if (this._scrolledToBottom)
    249             this._immediatelyScrollIntoView();
    250         else
    251             WebInspector.View.prototype.restoreScrollPositions.call(this);
    252     },
    253 
    254     onResize: function()
    255     {
    256         this.restoreScrollPositions();
    257     },
    258 
    259     _isScrollIntoViewScheduled: function()
    260     {
    261         return !!this._scrollIntoViewTimer;
    262     },
    263 
    264     _scheduleScrollIntoView: function()
    265     {
    266         if (this._scrollIntoViewTimer)
    267             return;
    268 
    269         function scrollIntoView()
    270         {
    271             delete this._scrollIntoViewTimer;
    272             this.promptElement.scrollIntoView(true);
    273         }
    274         this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20);
    275     },
    276 
    277     _immediatelyScrollIntoView: function()
    278     {
    279         this.promptElement.scrollIntoView(true);
    280         this._cancelScheduledScrollIntoView();
    281     },
    282 
    283     _cancelScheduledScrollIntoView: function()
    284     {
    285         if (!this._isScrollIntoViewScheduled())
    286             return;
    287 
    288         clearTimeout(this._scrollIntoViewTimer);
    289         delete this._scrollIntoViewTimer;
    290     },
    291 
    292     /**
    293      * @param {number=} count
    294      */
    295     _updateFilterStatus: function(count) {
    296         count = (typeof count === undefined) ? (WebInspector.console.messages.length - this._visibleMessagesIndices.length) : count;
    297         this._filterStatusTextElement.textContent = WebInspector.UIString(count == 1 ? "%d message is hidden by filters." : "%d messages are hidden by filters.", count);
    298         this._filterStatusMessageElement.style.display = count ? "" : "none";
    299     },
    300 
    301     /**
    302      * @param {WebInspector.Event} event
    303      */
    304     _consoleMessageAdded: function(event)
    305     {
    306         var message = /** @type {WebInspector.ConsoleMessage} */ (event.data);
    307         var index = message.index;
    308 
    309         if (this._urlToMessageCount[message.url])
    310             this._urlToMessageCount[message.url]++;
    311         else
    312             this._urlToMessageCount[message.url] = 1;
    313 
    314         if (this._filter.shouldBeVisible(message))
    315             this._showConsoleMessage(index);
    316         else
    317             this._updateFilterStatus();
    318     },
    319 
    320     _showConsoleMessage: function(index)
    321     {
    322         var message = WebInspector.console.messages[index];
    323 
    324         // this.messagesElement.isScrolledToBottom() is forcing style recalculation.
    325         // We just skip it if the scroll action has been scheduled.
    326         if (!this._isScrollIntoViewScheduled() && ((message instanceof WebInspector.ConsoleCommandResult) || this.messagesElement.isScrolledToBottom()))
    327             this._scheduleScrollIntoView();
    328 
    329         this._visibleMessagesIndices.push(index);
    330 
    331         if (message.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
    332             var parentGroup = this.currentGroup.parentGroup;
    333             if (parentGroup)
    334                 this.currentGroup = parentGroup;
    335         } else {
    336             if (message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
    337                 var group = new WebInspector.ConsoleGroup(this.currentGroup);
    338                 this.currentGroup.messagesElement.appendChild(group.element);
    339                 this.currentGroup = group;
    340                 message.group = group;
    341             }
    342             this.currentGroup.addMessage(message);
    343         }
    344 
    345         if (this._searchRegex && message.matchesRegex(this._searchRegex)) {
    346             this._searchResultsIndices.push(index);
    347             WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider);
    348         }
    349     },
    350 
    351     _consoleCleared: function()
    352     {
    353         this._scrolledToBottom = true;
    354         for (var i = 0; i < this._visibleMessagesIndices.length; ++i)
    355             WebInspector.console.messages[this._visibleMessagesIndices[i]].willHide();
    356         this._visibleMessagesIndices = [];
    357         this._searchResultsIndices = [];
    358 
    359         if (this._searchRegex)
    360             WebInspector.searchController.updateSearchMatchesCount(0, this._searchProvider);
    361 
    362         this.currentGroup = this.topGroup;
    363         this.topGroup.messagesElement.removeChildren();
    364 
    365         this._clearCurrentSearchResultHighlight();
    366         this._updateFilterStatus(0);
    367 
    368         this._linkifier.reset();
    369     },
    370 
    371     _handleContextMenuEvent: function(event)
    372     {
    373         if (!window.getSelection().isCollapsed) {
    374             // If there is a selection, we want to show our normal context menu
    375             // (with Copy, etc.), and not Clear Console.
    376             return;
    377         }
    378 
    379         if (event.target.enclosingNodeOrSelfWithNodeName("a"))
    380             return;
    381 
    382         var contextMenu = new WebInspector.ContextMenu(event);
    383 
    384         function monitoringXHRItemAction()
    385         {
    386             WebInspector.settings.monitoringXHREnabled.set(!WebInspector.settings.monitoringXHREnabled.get());
    387         }
    388         contextMenu.appendCheckboxItem(WebInspector.UIString("Log XMLHttpRequests"), monitoringXHRItemAction.bind(this), WebInspector.settings.monitoringXHREnabled.get());
    389 
    390         function preserveLogItemAction()
    391         {
    392             WebInspector.settings.preserveConsoleLog.set(!WebInspector.settings.preserveConsoleLog.get());
    393         }
    394         contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Preserve log upon navigation" : "Preserve Log upon Navigation"), preserveLogItemAction.bind(this), WebInspector.settings.preserveConsoleLog.get());
    395 
    396         var sourceElement = event.target.enclosingNodeOrSelfWithClass("console-message");
    397 
    398         var filterSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Filter"));
    399 
    400         if (sourceElement && sourceElement.message.url) {
    401             var menuTitle = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Hide messages from %s" : "Hide Messages from %s", new WebInspector.ParsedURL(sourceElement.message.url).displayName);
    402             filterSubMenu.appendItem(menuTitle, this._filter.addMessageURLFilter.bind(this._filter, sourceElement.message.url));
    403         }
    404 
    405         filterSubMenu.appendSeparator();
    406         var unhideAll = filterSubMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Unhide all" : "Unhide All"), this._filter.removeMessageURLFilter.bind(this._filter));
    407         filterSubMenu.appendSeparator();
    408 
    409         var hasFilters = false;
    410 
    411         for (var url in this._filter.messageURLFilters) {
    412             filterSubMenu.appendCheckboxItem(String.sprintf("%s (%d)", new WebInspector.ParsedURL(url).displayName, this._urlToMessageCount[url]), this._filter.removeMessageURLFilter.bind(this._filter, url), true);
    413             hasFilters = true;
    414         }
    415 
    416         filterSubMenu.setEnabled(hasFilters || (sourceElement && sourceElement.message.url));
    417         unhideAll.setEnabled(hasFilters);
    418 
    419         contextMenu.appendSeparator();
    420         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear console" : "Clear Console"), this._requestClearMessages.bind(this));
    421 
    422         var request = (sourceElement && sourceElement.message) ? sourceElement.message.request() : null;
    423         if (request && request.type === WebInspector.resourceTypes.XHR) {
    424             contextMenu.appendSeparator();
    425             contextMenu.appendItem(WebInspector.UIString("Replay XHR"), NetworkAgent.replayXHR.bind(null, request.requestId));
    426         }
    427 
    428         contextMenu.show();
    429     },
    430 
    431     _updateMessageList: function()
    432     {
    433         var group = this.topGroup;
    434         var sourceMessages = WebInspector.console.messages;
    435         var visibleMessageIndex = 0;
    436         var newVisibleMessages = [];
    437 
    438         if (this._searchRegex)
    439             this._searchResultsIndices = [];
    440 
    441         var anchor = null;
    442         for (var i = 0; i < sourceMessages.length; ++i) {
    443             var sourceMessage = sourceMessages[i];
    444             var visibleMessage = WebInspector.console.messages[this._visibleMessagesIndices[visibleMessageIndex]];
    445 
    446             if (visibleMessage === sourceMessage) {
    447                 if (this._filter.shouldBeVisible(visibleMessage)) {
    448                     newVisibleMessages.push(this._visibleMessagesIndices[visibleMessageIndex]);
    449 
    450                     if (this._searchRegex && sourceMessage.matchesRegex(this._searchRegex))
    451                         this._searchResultsIndices.push(i);
    452 
    453                     if (sourceMessage.type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
    454                         anchor = group.element;
    455                         group = group.parentGroup || group;
    456                     } else if (sourceMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroup || sourceMessage.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
    457                         group = sourceMessage.group;
    458                         anchor = group.messagesElement.firstChild;
    459                     } else
    460                         anchor = visibleMessage.toMessageElement();
    461                 } else {
    462                     visibleMessage.willHide();
    463                     visibleMessage.toMessageElement().remove();
    464                 }
    465                 ++visibleMessageIndex;
    466             } else {
    467                 if (this._filter.shouldBeVisible(sourceMessage)) {
    468 
    469                     if (this._searchRegex && sourceMessage.matchesRegex(this._searchRegex))
    470                         this._searchResultsIndices.push(i);
    471 
    472                     group.addMessage(sourceMessage, anchor ? anchor.nextSibling : group.messagesElement.firstChild);
    473                     newVisibleMessages.push(i);
    474                     anchor = sourceMessage.toMessageElement();
    475                 }
    476             }
    477         }
    478 
    479         if (this._searchRegex)
    480             WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider);
    481 
    482         this._visibleMessagesIndices = newVisibleMessages;
    483         this._updateFilterStatus();
    484     },
    485 
    486     _monitoringXHREnabledSettingChanged: function(event)
    487     {
    488         ConsoleAgent.setMonitoringXHREnabled(event.data);
    489     },
    490 
    491     _messagesClicked: function()
    492     {
    493         if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
    494             this.prompt.moveCaretToEndOfPrompt();
    495     },
    496 
    497     _registerShortcuts: function()
    498     {
    499         this._shortcuts = {};
    500 
    501         var shortcut = WebInspector.KeyboardShortcut;
    502         var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Console"));
    503 
    504         var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
    505         this._shortcuts[shortcutL.key] = this._requestClearMessages.bind(this);
    506         var keys = [shortcutL];
    507         if (WebInspector.isMac()) {
    508             var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
    509             this._shortcuts[shortcutK.key] = this._requestClearMessages.bind(this);
    510             keys.unshift(shortcutK);
    511         }
    512         section.addAlternateKeys(keys, WebInspector.UIString("Clear console"));
    513 
    514         section.addKey(shortcut.makeDescriptor(shortcut.Keys.Tab), WebInspector.UIString("Autocomplete common prefix"));
    515         section.addKey(shortcut.makeDescriptor(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
    516 
    517         keys = [
    518             shortcut.makeDescriptor(shortcut.Keys.Down),
    519             shortcut.makeDescriptor(shortcut.Keys.Up)
    520         ];
    521         section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
    522 
    523         if (WebInspector.isMac()) {
    524             keys = [
    525                 shortcut.makeDescriptor("N", shortcut.Modifiers.Alt),
    526                 shortcut.makeDescriptor("P", shortcut.Modifiers.Alt)
    527             ];
    528             section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
    529         }
    530 
    531         section.addKey(shortcut.makeDescriptor(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
    532     },
    533 
    534     _requestClearMessages: function()
    535     {
    536         WebInspector.console.requestClearMessages();
    537     },
    538 
    539     _promptKeyDown: function(event)
    540     {
    541         if (isEnterKey(event)) {
    542             this._enterKeyPressed(event);
    543             return;
    544         }
    545 
    546         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
    547         var handler = this._shortcuts[shortcut];
    548         if (handler) {
    549             handler();
    550             event.preventDefault();
    551         }
    552     },
    553 
    554     evaluateUsingTextPrompt: function(expression, showResultOnly)
    555     {
    556         this._appendCommand(expression, this.prompt.text, false, showResultOnly);
    557     },
    558 
    559     _enterKeyPressed: function(event)
    560     {
    561         if (event.altKey || event.ctrlKey || event.shiftKey)
    562             return;
    563 
    564         event.consume(true);
    565 
    566         this.prompt.clearAutoComplete(true);
    567 
    568         var str = this.prompt.text;
    569         if (!str.length)
    570             return;
    571         this._appendCommand(str, "", true, false);
    572     },
    573 
    574     _printResult: function(result, wasThrown, originatingCommand)
    575     {
    576         if (!result)
    577             return;
    578         var message = new WebInspector.ConsoleCommandResult(result, wasThrown, originatingCommand, this._linkifier);
    579         WebInspector.console.addMessage(message);
    580     },
    581 
    582     _appendCommand: function(text, newPromptText, useCommandLineAPI, showResultOnly)
    583     {
    584         if (!showResultOnly) {
    585             var commandMessage = new WebInspector.ConsoleCommand(text);
    586             WebInspector.console.addMessage(commandMessage);
    587         }
    588         this.prompt.text = newPromptText;
    589 
    590         function printResult(result, wasThrown)
    591         {
    592             if (!result)
    593                 return;
    594 
    595             if (!showResultOnly) {
    596                 this.prompt.pushHistoryItem(text);
    597                 WebInspector.settings.consoleHistory.set(this.prompt.historyData.slice(-30));
    598             }
    599 
    600             this._printResult(result, wasThrown, commandMessage);
    601         }
    602         WebInspector.runtimeModel.evaluate(text, "console", useCommandLineAPI, false, false, true, printResult.bind(this));
    603 
    604         WebInspector.userMetrics.ConsoleEvaluated.record();
    605     },
    606 
    607     elementsToRestoreScrollPositionsFor: function()
    608     {
    609         return [this.messagesElement];
    610     },
    611 
    612     searchCanceled: function()
    613     {
    614         this._clearCurrentSearchResultHighlight();
    615         delete this._searchProvider;
    616         delete this._searchResultsIndices;
    617         delete this._searchRegex;
    618     },
    619 
    620     canSearchAndReplace: function()
    621     {
    622         return false;
    623     },
    624 
    625     canFilter: function()
    626     {
    627         return true;
    628     },
    629 
    630     /**
    631      * @param {string} query
    632      * @param {boolean} shouldJump
    633      * @param {WebInspector.Searchable=} self
    634      */
    635     performSearch: function(query, shouldJump, self)
    636     {
    637         this.searchCanceled();
    638         this._searchProvider = self || this;
    639         WebInspector.searchController.updateSearchMatchesCount(0, this._searchProvider);
    640         this._searchRegex = createPlainTextSearchRegex(query, "gi");
    641 
    642         this._searchResultsIndices = [];
    643         for (var i = 0; i < this._visibleMessagesIndices.length; i++) {
    644             if (WebInspector.console.messages[this._visibleMessagesIndices[i]].matchesRegex(this._searchRegex))
    645                 this._searchResultsIndices.push(this._visibleMessagesIndices[i]);
    646         }
    647         WebInspector.searchController.updateSearchMatchesCount(this._searchResultsIndices.length, this._searchProvider);
    648         this._currentSearchResultIndex = -1;
    649         if (shouldJump && this._searchResultsIndices.length)
    650             this._jumpToSearchResult(0, self);
    651     },
    652 
    653     /**
    654      * @return {number}
    655      */
    656     minimalSearchQuerySize: function()
    657     {
    658         return 0;
    659     },
    660 
    661     /**
    662      * @param {string} query
    663      */
    664     performFilter: function(query)
    665     {
    666         this._filter.performFilter(query);
    667     },
    668 
    669     /**
    670      * @param {WebInspector.Searchable=} self
    671      */
    672     jumpToNextSearchResult: function(self)
    673     {
    674         if (!this._searchResultsIndices || !this._searchResultsIndices.length)
    675             return;
    676         this._jumpToSearchResult((this._currentSearchResultIndex + 1) % this._searchResultsIndices.length, self);
    677     },
    678 
    679     /**
    680      * @param {WebInspector.Searchable=} self
    681      */
    682     jumpToPreviousSearchResult: function(self)
    683     {
    684         if (!this._searchResultsIndices || !this._searchResultsIndices.length)
    685             return;
    686         var index = this._currentSearchResultIndex - 1;
    687         if (index === -1)
    688             index = this._searchResultsIndices.length - 1;
    689         this._jumpToSearchResult(index, self);
    690     },
    691 
    692     _clearCurrentSearchResultHighlight: function()
    693     {
    694         if (!this._searchResultsIndices)
    695             return;
    696         var highlightedMessage = WebInspector.console.messages[this._searchResultsIndices[this._currentSearchResultIndex]];
    697         if (highlightedMessage)
    698             highlightedMessage.clearHighlight();
    699         this._currentSearchResultIndex = -1;
    700     },
    701 
    702     _jumpToSearchResult: function(index, self)
    703     {
    704         this._clearCurrentSearchResultHighlight();
    705         this._currentSearchResultIndex = index;
    706         WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this._searchProvider);
    707         WebInspector.console.messages[this._searchResultsIndices[index]].highlightSearchResults(this._searchRegex);
    708     },
    709 
    710     __proto__: WebInspector.View.prototype
    711 }
    712 
    713 /**
    714  * @extends {WebInspector.Object}
    715  * @constructor
    716  */
    717 WebInspector.ConsoleViewFilter = function()
    718 {
    719     this._messageURLFilters = WebInspector.settings.messageURLFilters.get();
    720     this._messageSourceFilters = WebInspector.settings.messageSourceFilters.get();
    721     this._messageLevelFilters = WebInspector.settings.messageLevelFilters.get();
    722 
    723     this._sourceToKeyMap = {};
    724 
    725     for (var key in WebInspector.ConsoleViewFilter._messageSourceGroups) {
    726         if (!WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources) {
    727             console.assert(!this._otherKey);
    728             this._otherKey = key;
    729             continue;
    730         }
    731 
    732         for (var i = 0; i < WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources.length; ++i)
    733             this._sourceToKeyMap[WebInspector.ConsoleViewFilter._messageSourceGroups[key].sources[i]] = key;
    734     }
    735 
    736     this._filterChanged = this.dispatchEventToListeners.bind(this, WebInspector.ConsoleViewFilter.Events.FilterChanged);
    737 
    738     WebInspector.settings.messageSourceFilters.addChangeListener(this._updateSourceFilterButton.bind(this));
    739     WebInspector.settings.messageLevelFilters.addChangeListener(this._updateLevelFilterBar.bind(this));
    740 
    741     this.sourceFilterButton = new WebInspector.StatusBarButton(WebInspector.UIString("Filter"), "console-filter", 2);
    742     this.sourceFilterButton.element.addEventListener("mousedown", this._handleSourceFilterButtonClick.bind(this), false);
    743 
    744     this._filterBarElements = [];
    745 
    746     this.filterBarElement = document.createElement("div");
    747     this.filterBarElement.className = "scope-bar status-bar-item";
    748 
    749     this._createLevelFilterBarElement("all", WebInspector.UIString("All"));
    750 
    751     var dividerElement = document.createElement("div");
    752     dividerElement.addStyleClass("scope-bar-divider");
    753     this.filterBarElement.appendChild(dividerElement);
    754 
    755     this._createLevelFilterBarElement("error", WebInspector.UIString("Errors"));
    756     this._createLevelFilterBarElement("warning", WebInspector.UIString("Warnings"));
    757     this._createLevelFilterBarElement("log", WebInspector.UIString("Logs"));
    758     this._createLevelFilterBarElement("debug", WebInspector.UIString("Debug"));
    759 
    760     this._updateLevelFilterBar();
    761     this._updateSourceFilterButton();
    762 };
    763 
    764 WebInspector.ConsoleViewFilter.Events = {
    765     FilterChanged: "FilterChanged"
    766 };
    767 
    768 WebInspector.ConsoleViewFilter._messageSourceGroups = {
    769     JS: { sources: [WebInspector.ConsoleMessage.MessageSource.JS], title: "JavaScript", styleClass: "filter-type-javascript"},
    770     Network: { sources: [WebInspector.ConsoleMessage.MessageSource.Network], title: "Network", styleClass: "filter-type-network"},
    771     Logging: { sources: [WebInspector.ConsoleMessage.MessageSource.ConsoleAPI], title: "Logging", styleClass: "filter-type-logging"},
    772     CSS: { sources: [WebInspector.ConsoleMessage.MessageSource.CSS], title: "CSS", styleClass: "filter-type-css"},
    773     Other: { title: "Other", styleClass: "filter-type-other"}
    774 };
    775 
    776 WebInspector.ConsoleViewFilter.prototype = {
    777     /**
    778      * @param {string} url
    779      */
    780     addMessageURLFilter: function(url)
    781     {
    782         this._messageURLFilters[url] = true;
    783         WebInspector.settings.messageURLFilters.set(this._messageURLFilters);
    784         this._filterChanged();
    785     },
    786 
    787     /**
    788      * @param {string} url
    789      */
    790     removeMessageURLFilter: function(url)
    791     {
    792         if (!url)
    793             this._messageURLFilters = {};
    794         else
    795             delete this._messageURLFilters[url];
    796 
    797         WebInspector.settings.messageURLFilters.set(this._messageURLFilters);
    798         this._filterChanged();
    799     },
    800 
    801     /**
    802      * @returns {Object}
    803      */
    804     get messageURLFilters()
    805     {
    806         return this._messageURLFilters;
    807     },
    808 
    809     /**
    810      * @param {WebInspector.ConsoleMessage} message
    811      * @return {boolean}
    812      */
    813     shouldBeVisible: function(message)
    814     {
    815         if ((message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed || message.type === WebInspector.ConsoleMessage.MessageType.EndGroup))
    816             return true;
    817 
    818         if (message.url && this._messageURLFilters[message.url])
    819             return false;
    820 
    821         if (message.level && this._messageLevelFilters[message.level])
    822             return false;
    823 
    824         if (this._filterRegex) {
    825             this._filterRegex.lastIndex = 0;
    826             if (!message.matchesRegex(this._filterRegex))
    827                 return false;
    828         }
    829 
    830         // We store group keys, and we have resolved group by message source
    831         if (message.source) {
    832             if (this._sourceToKeyMap[message.source])
    833                 return !this._messageSourceFilters[this._sourceToKeyMap[message.source]];
    834             else
    835                 return !this._messageSourceFilters[this._otherKey];
    836         }
    837 
    838 
    839         return true;
    840     },
    841 
    842     reset: function()
    843     {
    844         this._messageSourceFilters = {};
    845         WebInspector.settings.messageSourceFilters.set(this._messageSourceFilters);
    846         this._messageURLFilters = {};
    847         WebInspector.settings.messageURLFilters.set(this._messageURLFilters);
    848         this._messageLevelFilters = {};
    849         WebInspector.settings.messageLevelFilters.set(this._messageLevelFilters);
    850         this._filterChanged();
    851     },
    852 
    853     /**
    854      * @param {string} query
    855      */
    856     performFilter: function(query)
    857     {
    858         if (!query)
    859             delete this._filterRegex;
    860         else
    861             this._filterRegex = createPlainTextSearchRegex(query, "gi");
    862 
    863         this._filterChanged();
    864     },
    865 
    866     /**
    867      * @param {string} sourceGroup
    868      * @private
    869      */
    870     _toggleMessageSourceFilter: function(sourceGroup)
    871     {
    872         if (!this._messageSourceFilters[sourceGroup])
    873             this._messageSourceFilters[sourceGroup] = true;
    874         else
    875             delete this._messageSourceFilters[sourceGroup];
    876 
    877         WebInspector.settings.messageSourceFilters.set(this._messageSourceFilters);
    878         this._filterChanged();
    879     },
    880 
    881     /**
    882      * @private
    883      */
    884     _updateSourceFilterButton: function()
    885     {
    886         var hasActiveSourceFilter = false;
    887         for (var sourceGroup in WebInspector.ConsoleViewFilter._messageSourceGroups) {
    888             if (this._messageSourceFilters[sourceGroup]) {
    889                 hasActiveSourceFilter = true;
    890                 break;
    891             }
    892         }
    893 
    894         this.sourceFilterButton.state = hasActiveSourceFilter;
    895     },
    896 
    897     /**
    898      * @param {Event} event
    899      * @returns {WebInspector.ContextMenu}
    900      * @private
    901      */
    902     _createSourceFilterMenu: function(event)
    903     {
    904         var menu = new WebInspector.ContextMenu(event);
    905 
    906         for (var sourceGroup in WebInspector.ConsoleViewFilter._messageSourceGroups) {
    907             var filter = WebInspector.ConsoleViewFilter._messageSourceGroups[sourceGroup];
    908 
    909             menu.appendCheckboxItem(WebInspector.UIString(WebInspector.UIString(filter.title)), this._toggleMessageSourceFilter.bind(this, sourceGroup), !this._messageSourceFilters[sourceGroup]);
    910         }
    911 
    912         return menu;
    913     },
    914 
    915     /**
    916      * @param {string} level
    917      * @param {string} label
    918      * @private
    919      */
    920     _createLevelFilterBarElement: function(level, label)
    921     {
    922         var categoryElement = document.createElement("li");
    923         categoryElement.category = level;
    924         categoryElement.className = level;
    925         categoryElement.textContent = label;
    926         categoryElement.addEventListener("click", this._toggleLevelFilter.bind(this, level), false);
    927 
    928         this._filterBarElements[level] = categoryElement;
    929         this.filterBarElement.appendChild(categoryElement);
    930     },
    931 
    932     /**
    933      * @param {string} level
    934      * @param {Event} event
    935      * @private
    936      */
    937     _toggleLevelFilter: function(level, event)
    938     {
    939         var isMac = WebInspector.isMac();
    940         var selectMultiple = false;
    941         if (isMac && event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey)
    942             selectMultiple = true;
    943         if (!isMac && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey)
    944             selectMultiple = true;
    945 
    946         if (level === "all")
    947             this._messageLevelFilters = {};
    948         else {
    949             if (!selectMultiple) {
    950                 this._messageLevelFilters = {error: true, warning: true, log: true, debug: true};
    951                 delete this._messageLevelFilters[level];
    952             } else {
    953                 if (this._messageLevelFilters[level])
    954                     delete this._messageLevelFilters[level];
    955                 else
    956                     this._messageLevelFilters[level] = true;
    957             }
    958         }
    959 
    960         WebInspector.settings.messageLevelFilters.set(this._messageLevelFilters);
    961         this._filterChanged();
    962     },
    963 
    964     /**
    965      * @private
    966      */
    967     _updateLevelFilterBar: function()
    968     {
    969         var all = !(this._messageLevelFilters["error"] || this._messageLevelFilters["warning"] || this._messageLevelFilters["log"] || this._messageLevelFilters["debug"]);
    970 
    971         this._filterBarElements["all"].enableStyleClass("selected", all);
    972 
    973         this._filterBarElements["error"].enableStyleClass("selected", !all && !this._messageLevelFilters["error"]);
    974         this._filterBarElements["warning"].enableStyleClass("selected", !all && !this._messageLevelFilters["warning"]);
    975         this._filterBarElements["log"].enableStyleClass("selected", !all && !this._messageLevelFilters["log"]);
    976         this._filterBarElements["debug"].enableStyleClass("selected", !all && !this._messageLevelFilters["debug"]);
    977     },
    978 
    979     /**
    980      * @param {Event} event
    981      * @private
    982      */
    983     _handleSourceFilterButtonClick: function(event)
    984     {
    985         if (!event.button)
    986             this._createSourceFilterMenu(event).showSoftMenu();
    987     },
    988 
    989     __proto__: WebInspector.Object.prototype
    990 };
    991 
    992 
    993 /**
    994  * @constructor
    995  * @extends WebInspector.ConsoleMessage
    996  */
    997 WebInspector.ConsoleCommand = function(text)
    998 {
    999     this.text = text;
   1000 }
   1001 
   1002 WebInspector.ConsoleCommand.prototype = {
   1003     wasShown: function()
   1004     {
   1005     },
   1006 
   1007     willHide: function()
   1008     {
   1009     },
   1010 
   1011     clearHighlight: function()
   1012     {
   1013         var highlightedMessage = this._formattedCommand;
   1014         delete this._formattedCommand;
   1015         this._formatCommand();
   1016         this._element.replaceChild(this._formattedCommand, highlightedMessage);
   1017     },
   1018 
   1019     highlightSearchResults: function(regexObject)
   1020     {
   1021         regexObject.lastIndex = 0;
   1022         var match = regexObject.exec(this.text);
   1023         var matchRanges = [];
   1024         while (match) {
   1025             matchRanges.push({ offset: match.index, length: match[0].length });
   1026             match = regexObject.exec(this.text);
   1027         }
   1028         WebInspector.highlightSearchResults(this._formattedCommand, matchRanges);
   1029         this._element.scrollIntoViewIfNeeded();
   1030     },
   1031 
   1032     matchesRegex: function(regexObject)
   1033     {
   1034         regexObject.lastIndex = 0;
   1035         return regexObject.test(this.text);
   1036     },
   1037 
   1038     toMessageElement: function()
   1039     {
   1040         if (!this._element) {
   1041             this._element = document.createElement("div");
   1042             this._element.command = this;
   1043             this._element.className = "console-user-command";
   1044 
   1045             this._formatCommand();
   1046             this._element.appendChild(this._formattedCommand);
   1047         }
   1048         return this._element;
   1049     },
   1050 
   1051     _formatCommand: function()
   1052     {
   1053         this._formattedCommand = document.createElement("span");
   1054         this._formattedCommand.className = "console-message-text source-code";
   1055         this._formattedCommand.textContent = this.text;
   1056     },
   1057 
   1058     __proto__: WebInspector.ConsoleMessage.prototype
   1059 }
   1060 
   1061 /**
   1062  * @extends {WebInspector.ConsoleMessageImpl}
   1063  * @constructor
   1064  * @param {boolean} result
   1065  * @param {boolean} wasThrown
   1066  * @param {WebInspector.ConsoleCommand} originatingCommand
   1067  * @param {WebInspector.Linkifier} linkifier
   1068  */
   1069 WebInspector.ConsoleCommandResult = function(result, wasThrown, originatingCommand, linkifier)
   1070 {
   1071     var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log);
   1072     this.originatingCommand = originatingCommand;
   1073     WebInspector.ConsoleMessageImpl.call(this, WebInspector.ConsoleMessage.MessageSource.JS, level, "", linkifier, WebInspector.ConsoleMessage.MessageType.Result, undefined, undefined, undefined, undefined, [result]);
   1074 }
   1075 
   1076 WebInspector.ConsoleCommandResult.prototype = {
   1077     /**
   1078      * @override
   1079      * @param {WebInspector.RemoteObject} array
   1080      * @return {boolean}
   1081      */
   1082     useArrayPreviewInFormatter: function(array)
   1083     {
   1084         return false;
   1085     },
   1086 
   1087     toMessageElement: function()
   1088     {
   1089         var element = WebInspector.ConsoleMessageImpl.prototype.toMessageElement.call(this);
   1090         element.addStyleClass("console-user-command-result");
   1091         return element;
   1092     },
   1093 
   1094     __proto__: WebInspector.ConsoleMessageImpl.prototype
   1095 }
   1096 
   1097 /**
   1098  * @constructor
   1099  */
   1100 WebInspector.ConsoleGroup = function(parentGroup)
   1101 {
   1102     this.parentGroup = parentGroup;
   1103 
   1104     var element = document.createElement("div");
   1105     element.className = "console-group";
   1106     element.group = this;
   1107     this.element = element;
   1108 
   1109     if (parentGroup) {
   1110         var bracketElement = document.createElement("div");
   1111         bracketElement.className = "console-group-bracket";
   1112         element.appendChild(bracketElement);
   1113     }
   1114 
   1115     var messagesElement = document.createElement("div");
   1116     messagesElement.className = "console-group-messages";
   1117     element.appendChild(messagesElement);
   1118     this.messagesElement = messagesElement;
   1119 }
   1120 
   1121 WebInspector.ConsoleGroup.prototype = {
   1122     /**
   1123      * @param {WebInspector.ConsoleMessage} message
   1124      * @param {Node=} node
   1125      */
   1126     addMessage: function(message, node)
   1127     {
   1128         var element = message.toMessageElement();
   1129 
   1130         if (message.type === WebInspector.ConsoleMessage.MessageType.StartGroup || message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) {
   1131             this.messagesElement.parentNode.insertBefore(element, this.messagesElement);
   1132             element.addEventListener("click", this._titleClicked.bind(this), false);
   1133             var groupElement = element.enclosingNodeOrSelfWithClass("console-group");
   1134             if (groupElement && message.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed)
   1135                 groupElement.addStyleClass("collapsed");
   1136         } else {
   1137             this.messagesElement.insertBefore(element, node || null);
   1138             message.wasShown();
   1139         }
   1140 
   1141         if (element.previousSibling && message.originatingCommand && element.previousSibling.command === message.originatingCommand)
   1142             element.previousSibling.addStyleClass("console-adjacent-user-command-result");
   1143     },
   1144 
   1145     _titleClicked: function(event)
   1146     {
   1147         var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title");
   1148         if (groupTitleElement) {
   1149             var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group");
   1150             if (groupElement)
   1151                 if (groupElement.hasStyleClass("collapsed"))
   1152                     groupElement.removeStyleClass("collapsed");
   1153                 else
   1154                     groupElement.addStyleClass("collapsed");
   1155             groupTitleElement.scrollIntoViewIfNeeded(true);
   1156         }
   1157 
   1158         event.consume(true);
   1159     }
   1160 }
   1161 
   1162 /**
   1163  * @type {?WebInspector.ConsoleView}
   1164  */
   1165 WebInspector.consoleView = null;
   1166 
   1167 WebInspector.ConsoleMessage.create = function(source, level, message, type, url, line, column, repeatCount, parameters, stackTrace, requestId, isOutdated)
   1168 {
   1169     return new WebInspector.ConsoleMessageImpl(source, level, message, WebInspector.consoleView._linkifier, type, url, line, column, repeatCount, parameters, stackTrace, requestId, isOutdated);
   1170 }
   1171