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