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