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