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