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