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 */
37WebInspector.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
149WebInspector.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"), request.replayXHR.bind(request));
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 */
954WebInspector.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
961WebInspector.ConsoleViewFilter.Events = {
962    FilterChanged: "FilterChanged"
963};
964
965WebInspector.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 */
1083WebInspector.ConsoleCommand = function(message, nestingLevel)
1084{
1085    WebInspector.ConsoleViewMessage.call(this, message, null, nestingLevel);
1086}
1087
1088WebInspector.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 */
1154WebInspector.ConsoleCommandResult = function(message, linkifier, nestingLevel)
1155{
1156    WebInspector.ConsoleViewMessage.call(this, message, linkifier, nestingLevel);
1157}
1158
1159WebInspector.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 */
1188WebInspector.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 */
1198WebInspector.ConsoleGroup.createTopGroup = function()
1199{
1200    return new WebInspector.ConsoleGroup(null, null);
1201}
1202
1203WebInspector.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 */
1233WebInspector.ConsoleView.ShowConsoleActionDelegate = function()
1234{
1235}
1236
1237WebInspector.ConsoleView.ShowConsoleActionDelegate.prototype = {
1238    /**
1239     * @return {boolean}
1240     */
1241    handleAction: function()
1242    {
1243        WebInspector.console.show();
1244        return true;
1245    }
1246}
1247