Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / console / ConsoleView.js
blob02fdc59a93b54f1c3cfe65514f53d842e61c0c23
1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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.
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.
30 /**
31 * @constructor
32 * @extends {WebInspector.VBox}
33 * @implements {WebInspector.Searchable}
34 * @implements {WebInspector.TargetManager.Observer}
35 * @implements {WebInspector.ViewportControl.Provider}
37 WebInspector.ConsoleView = function()
39 WebInspector.VBox.call(this);
40 this.setMinimumSize(0, 35);
41 this.registerRequiredCSS("ui/filter.css");
42 this.registerRequiredCSS("console/consoleView.css");
44 this._searchableView = new WebInspector.SearchableView(this);
45 this._searchableView.setPlaceholder(WebInspector.UIString("Find string in logs"));
46 this._searchableView.setMinimalSearchQuerySize(0);
47 this._searchableView.show(this.element);
49 this._contentsElement = this._searchableView.element;
50 this._contentsElement.classList.add("console-view");
51 /** @type {!Array.<!WebInspector.ConsoleViewMessage>} */
52 this._visibleViewMessages = [];
53 this._urlToMessageCount = {};
54 this._hiddenByFilterCount = 0;
56 /**
57 * @type {!Array.<!WebInspector.ConsoleView.RegexMatchRange>}
59 this._regexMatchRanges = [];
61 this._clearConsoleButton = WebInspector.ToolbarButton.createActionButton("console.clear");
63 this._executionContextComboBox = new WebInspector.ToolbarComboBox(null, "console-context");
64 this._executionContextComboBox.setMaxWidth(200);
65 this._executionContextModel = new WebInspector.ExecutionContextModel(this._executionContextComboBox.selectElement());
67 this._filter = new WebInspector.ConsoleViewFilter(this);
68 this._filter.addEventListener(WebInspector.ConsoleViewFilter.Events.FilterChanged, this._updateMessageList.bind(this));
70 this._filterBar = new WebInspector.FilterBar("consoleView");
72 this._preserveLogCheckbox = new WebInspector.ToolbarCheckbox(WebInspector.UIString("Preserve log"), WebInspector.UIString("Do not clear log on page reload / navigation"), WebInspector.moduleSetting("preserveConsoleLog"));
73 this._progressToolbarItem = new WebInspector.ToolbarItem(createElement("div"));
75 var toolbar = new WebInspector.Toolbar(this._contentsElement);
76 toolbar.appendToolbarItem(this._clearConsoleButton);
77 toolbar.appendToolbarItem(this._filterBar.filterButton());
78 toolbar.appendToolbarItem(this._executionContextComboBox);
79 toolbar.appendToolbarItem(this._preserveLogCheckbox);
80 toolbar.appendToolbarItem(this._progressToolbarItem);
82 this._contentsElement.appendChild(this._filterBar.filtersElement());
83 this._filter.addFilters(this._filterBar);
85 this._viewport = new WebInspector.ViewportControl(this);
86 this._viewport.setStickToBottom(true);
87 this._viewport.contentElement().classList.add("console-group", "console-group-messages");
88 this._contentsElement.appendChild(this._viewport.element);
89 this._messagesElement = this._viewport.element;
90 this._messagesElement.id = "console-messages";
91 this._messagesElement.classList.add("monospace");
92 this._messagesElement.addEventListener("click", this._messagesClicked.bind(this), true);
94 this._viewportThrottler = new WebInspector.Throttler(50);
96 this._filterStatusMessageElement = createElementWithClass("div", "console-message");
97 this._messagesElement.insertBefore(this._filterStatusMessageElement, this._messagesElement.firstChild);
98 this._filterStatusTextElement = this._filterStatusMessageElement.createChild("span", "console-info");
99 this._filterStatusMessageElement.createTextChild(" ");
100 var resetFiltersLink = this._filterStatusMessageElement.createChild("span", "console-info link");
101 resetFiltersLink.textContent = WebInspector.UIString("Show all messages.");
102 resetFiltersLink.addEventListener("click", this._filter.reset.bind(this._filter), true);
104 this._topGroup = WebInspector.ConsoleGroup.createTopGroup();
105 this._currentGroup = this._topGroup;
107 this._promptElement = this._messagesElement.createChild("div", "source-code");
108 this._promptElement.id = "console-prompt";
109 this._promptElement.spellcheck = false;
111 this._searchableView.setDefaultFocusedElement(this._promptElement);
113 // FIXME: This is a workaround for the selection machinery bug. See crbug.com/410899
114 var selectAllFixer = this._messagesElement.createChild("div", "console-view-fix-select-all");
115 selectAllFixer.textContent = ".";
117 this._showAllMessagesCheckbox = new WebInspector.ToolbarCheckbox(WebInspector.UIString("Show all messages"));
118 this._showAllMessagesCheckbox.inputElement.checked = true;
119 this._showAllMessagesCheckbox.inputElement.addEventListener("change", this._updateMessageList.bind(this), false);
121 this._showAllMessagesCheckbox.element.classList.add("hidden");
123 toolbar.appendToolbarItem(this._showAllMessagesCheckbox);
125 this._registerShortcuts();
127 this._messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false);
128 WebInspector.moduleSetting("monitoringXHREnabled").addChangeListener(this._monitoringXHREnabledSettingChanged, this);
130 this._linkifier = new WebInspector.Linkifier();
132 /** @type {!Array.<!WebInspector.ConsoleViewMessage>} */
133 this._consoleMessages = [];
134 this._viewMessageSymbol = Symbol("viewMessage");
136 this._prompt = new WebInspector.TextPromptWithHistory(WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext);
137 this._prompt.setSuggestBoxEnabled(true);
138 this._prompt.setAutocompletionTimeout(0);
139 this._prompt.renderAsBlock();
140 var proxyElement = this._prompt.attach(this._promptElement);
141 proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false);
143 this._consoleHistorySetting = WebInspector.settings.createLocalSetting("consoleHistory", []);
144 var historyData = this._consoleHistorySetting.get();
145 this._prompt.setHistoryData(historyData);
147 this._updateFilterStatus();
148 WebInspector.moduleSetting("consoleTimestampsEnabled").addChangeListener(this._consoleTimestampsSettingChanged, this);
150 this._registerWithMessageSink();
151 WebInspector.targetManager.observeTargets(this);
152 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._onMainFrameNavigated, this);
154 this._initConsoleMessages();
156 WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext, this._executionContextChanged, this);
159 WebInspector.ConsoleView.persistedHistorySize = 300;
161 WebInspector.ConsoleView.prototype = {
163 * @return {!WebInspector.SearchableView}
165 searchableView: function()
167 return this._searchableView;
171 * @param {!WebInspector.Event} event
173 _onMainFrameNavigated: function(event)
175 var frame = /** @type {!WebInspector.ResourceTreeFrame} */(event.data);
176 WebInspector.console.addMessage(WebInspector.UIString("Navigated to %s", frame.url));
179 _initConsoleMessages: function()
181 var mainTarget = WebInspector.targetManager.mainTarget();
182 if (!mainTarget || !mainTarget.resourceTreeModel.cachedResourcesLoaded()) {
183 WebInspector.targetManager.addModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._onResourceTreeModelLoaded, this);
184 return;
186 this._fetchMultitargetMessages();
190 * @param {!WebInspector.Event} event
192 _onResourceTreeModelLoaded: function(event)
194 var resourceTreeModel = event.target;
195 if (resourceTreeModel.target() !== WebInspector.targetManager.mainTarget())
196 return;
197 WebInspector.targetManager.removeModelListener(WebInspector.ResourceTreeModel, WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._onResourceTreeModelLoaded, this);
198 this._fetchMultitargetMessages();
201 _fetchMultitargetMessages: function()
203 WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this);
204 WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this);
205 WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageUpdated, this._onConsoleMessageUpdated, this);
206 WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this);
207 WebInspector.multitargetConsoleModel.messages().forEach(this._addConsoleMessage, this);
211 * @override
212 * @return {number}
214 itemCount: function()
216 return this._visibleViewMessages.length;
220 * @override
221 * @param {number} index
222 * @return {?WebInspector.ViewportElement}
224 itemElement: function(index)
226 return this._visibleViewMessages[index];
230 * @override
231 * @param {number} index
232 * @return {number}
234 fastHeight: function(index)
236 return this._visibleViewMessages[index].fastHeight();
240 * @override
241 * @return {number}
243 minimumRowHeight: function()
245 return 16;
249 * @override
250 * @param {!WebInspector.Target} target
252 targetAdded: function(target)
254 this._viewport.invalidate();
255 if (WebInspector.targetManager.targets().length > 1 && WebInspector.targetManager.mainTarget().isPage())
256 this._showAllMessagesCheckbox.element.classList.toggle("hidden", false);
260 * @override
261 * @param {!WebInspector.Target} target
263 targetRemoved: function(target)
267 _registerWithMessageSink: function()
269 WebInspector.console.messages().forEach(this._addSinkMessage, this);
270 WebInspector.console.addEventListener(WebInspector.Console.Events.MessageAdded, messageAdded, this);
273 * @param {!WebInspector.Event} event
274 * @this {WebInspector.ConsoleView}
276 function messageAdded(event)
278 this._addSinkMessage(/** @type {!WebInspector.Console.Message} */ (event.data));
283 * @param {!WebInspector.Console.Message} message
285 _addSinkMessage: function(message)
287 var level = WebInspector.ConsoleMessage.MessageLevel.Debug;
288 switch (message.level) {
289 case WebInspector.Console.MessageLevel.Error:
290 level = WebInspector.ConsoleMessage.MessageLevel.Error;
291 break;
292 case WebInspector.Console.MessageLevel.Warning:
293 level = WebInspector.ConsoleMessage.MessageLevel.Warning;
294 break;
297 var consoleMessage = new WebInspector.ConsoleMessage(null, WebInspector.ConsoleMessage.MessageSource.Other, level, message.text,
298 undefined, undefined, undefined, undefined, undefined, undefined, undefined, message.timestamp);
299 this._addConsoleMessage(consoleMessage);
303 * @param {!WebInspector.Event} event
305 _consoleTimestampsSettingChanged: function(event)
307 var enabled = /** @type {boolean} */ (event.data);
308 this._updateMessageList();
309 this._consoleMessages.forEach(function(viewMessage) {
310 viewMessage.updateTimestamp(enabled);
315 * @override
316 * @return {!Element}
318 defaultFocusedElement: function()
320 return this._promptElement;
323 _executionContextChanged: function()
325 this._prompt.clearAutoComplete(true);
326 if (!this._showAllMessagesCheckbox.checked())
327 this._updateMessageList();
330 willHide: function()
332 this._hidePromptSuggestBox();
335 wasShown: function()
337 this._viewport.refresh();
338 if (!this._prompt.isCaretInsidePrompt())
339 this._prompt.moveCaretToEndOfPrompt();
342 focus: function()
344 if (this._promptElement === WebInspector.currentFocusElement())
345 return;
346 WebInspector.setCurrentFocusElement(this._promptElement);
347 this._prompt.moveCaretToEndOfPrompt();
350 restoreScrollPositions: function()
352 if (this._viewport.scrolledToBottom())
353 this._immediatelyScrollToBottom();
354 else
355 WebInspector.Widget.prototype.restoreScrollPositions.call(this);
358 onResize: function()
360 this._scheduleViewportRefresh();
361 this._hidePromptSuggestBox();
362 if (this._viewport.scrolledToBottom())
363 this._immediatelyScrollToBottom();
366 _hidePromptSuggestBox: function()
368 this._prompt.hideSuggestBox();
369 this._prompt.clearAutoComplete(true);
372 _scheduleViewportRefresh: function()
375 * @this {WebInspector.ConsoleView}
376 * @return {!Promise.<undefined>}
378 function invalidateViewport()
380 if (this._needsFullUpdate) {
381 this._updateMessageList();
382 delete this._needsFullUpdate;
383 } else {
384 this._viewport.invalidate();
386 return Promise.resolve();
388 this._viewportThrottler.schedule(invalidateViewport.bind(this));
391 _immediatelyScrollToBottom: function()
393 // This will scroll viewport and trigger its refresh.
394 this._promptElement.scrollIntoView(true);
397 _updateFilterStatus: function()
399 this._filterStatusTextElement.textContent = WebInspector.UIString(this._hiddenByFilterCount === 1 ? "%d message is hidden by filters." : "%d messages are hidden by filters.", this._hiddenByFilterCount);
400 this._filterStatusMessageElement.style.display = this._hiddenByFilterCount ? "" : "none";
404 * @param {!WebInspector.Event} event
406 _onConsoleMessageAdded: function(event)
408 var message = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
409 this._addConsoleMessage(message);
413 * @param {!WebInspector.ConsoleMessage} message
415 _addConsoleMessage: function(message)
418 * @param {!WebInspector.ConsoleViewMessage} viewMessage1
419 * @param {!WebInspector.ConsoleViewMessage} viewMessage2
420 * @return {number}
422 function compareTimestamps(viewMessage1, viewMessage2)
424 return WebInspector.ConsoleMessage.timestampComparator(viewMessage1.consoleMessage(), viewMessage2.consoleMessage());
427 if (message.type === WebInspector.ConsoleMessage.MessageType.Command || message.type === WebInspector.ConsoleMessage.MessageType.Result)
428 message.timestamp = this._consoleMessages.length ? this._consoleMessages.peekLast().consoleMessage().timestamp : 0;
429 var viewMessage = this._createViewMessage(message);
430 message[this._viewMessageSymbol] = viewMessage;
431 var insertAt = insertionIndexForObjectInListSortedByFunction(viewMessage, this._consoleMessages, compareTimestamps, true);
432 var insertedInMiddle = insertAt < this._consoleMessages.length;
433 this._consoleMessages.splice(insertAt, 0, viewMessage);
435 if (this._urlToMessageCount[message.url])
436 ++this._urlToMessageCount[message.url];
437 else
438 this._urlToMessageCount[message.url] = 1;
440 if (!insertedInMiddle) {
441 this._appendMessageToEnd(viewMessage);
442 this._updateFilterStatus();
443 this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length);
444 } else {
445 this._needsFullUpdate = true;
448 this._scheduleViewportRefresh();
449 this._consoleMessageAddedForTest(viewMessage);
453 * @param {!WebInspector.Event} event
455 _onConsoleMessageUpdated: function(event)
457 var message = /** @type {!WebInspector.ConsoleMessage} */ (event.data);
458 var viewMessage = message[this._viewMessageSymbol];
459 if (viewMessage) {
460 viewMessage.updateMessageElement();
461 this._updateMessageList();
466 * @param {!WebInspector.ConsoleViewMessage} viewMessage
468 _consoleMessageAddedForTest: function(viewMessage) { },
471 * @param {!WebInspector.ConsoleViewMessage} viewMessage
473 _appendMessageToEnd: function(viewMessage)
475 if (!this._filter.shouldBeVisible(viewMessage)) {
476 this._hiddenByFilterCount++;
477 return;
480 if (this._tryToCollapseMessages(viewMessage, this._visibleViewMessages.peekLast()))
481 return;
483 var lastMessage = this._visibleViewMessages.peekLast();
484 if (viewMessage.consoleMessage().type === WebInspector.ConsoleMessage.MessageType.EndGroup) {
485 if (lastMessage && !this._currentGroup.messagesHidden())
486 lastMessage.incrementCloseGroupDecorationCount();
487 this._currentGroup = this._currentGroup.parentGroup();
488 return;
490 if (!this._currentGroup.messagesHidden()) {
491 var originatingMessage = viewMessage.consoleMessage().originatingMessage();
492 if (lastMessage && originatingMessage && lastMessage.consoleMessage() === originatingMessage)
493 lastMessage.toMessageElement().classList.add("console-adjacent-user-command-result");
495 this._visibleViewMessages.push(viewMessage);
496 this._searchMessage(this._visibleViewMessages.length - 1);
499 if (viewMessage.consoleMessage().isGroupStartMessage())
500 this._currentGroup = new WebInspector.ConsoleGroup(this._currentGroup, viewMessage);
502 this._messageAppendedForTests();
505 _messageAppendedForTests: function()
507 // This method is sniffed in tests.
511 * @param {!WebInspector.ConsoleMessage} message
512 * @return {!WebInspector.ConsoleViewMessage}
514 _createViewMessage: function(message)
516 var nestingLevel = this._currentGroup.nestingLevel();
517 switch (message.type) {
518 case WebInspector.ConsoleMessage.MessageType.Command:
519 return new WebInspector.ConsoleCommand(message, this._linkifier, nestingLevel);
520 case WebInspector.ConsoleMessage.MessageType.Result:
521 return new WebInspector.ConsoleCommandResult(message, this._linkifier, nestingLevel);
522 case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed:
523 case WebInspector.ConsoleMessage.MessageType.StartGroup:
524 return new WebInspector.ConsoleGroupViewMessage(message, this._linkifier, nestingLevel);
525 default:
526 return new WebInspector.ConsoleViewMessage(message, this._linkifier, nestingLevel);
530 _consoleCleared: function()
532 this._currentMatchRangeIndex = -1;
533 this._consoleMessages = [];
534 this._updateMessageList();
535 this._hidePromptSuggestBox();
536 this._linkifier.reset();
539 _handleContextMenuEvent: function(event)
541 if (event.target.enclosingNodeOrSelfWithNodeName("a"))
542 return;
544 var contextMenu = new WebInspector.ContextMenu(event);
545 if (event.target.isSelfOrDescendant(this._promptElement)) {
546 contextMenu.show()
547 return;
550 function monitoringXHRItemAction()
552 WebInspector.moduleSetting("monitoringXHREnabled").set(!WebInspector.moduleSetting("monitoringXHREnabled").get());
554 contextMenu.appendCheckboxItem(WebInspector.UIString("Log XMLHttpRequests"), monitoringXHRItemAction, WebInspector.moduleSetting("monitoringXHREnabled").get());
556 var sourceElement = event.target.enclosingNodeOrSelfWithClass("console-message-wrapper");
557 var consoleMessage = sourceElement ? sourceElement.message.consoleMessage() : null;
559 var filterSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Filter"));
561 if (consoleMessage && consoleMessage.url) {
562 var menuTitle = WebInspector.UIString.capitalize("Hide ^messages from %s", new WebInspector.ParsedURL(consoleMessage.url).displayName);
563 filterSubMenu.appendItem(menuTitle, this._filter.addMessageURLFilter.bind(this._filter, consoleMessage.url));
566 filterSubMenu.appendSeparator();
567 var unhideAll = filterSubMenu.appendItem(WebInspector.UIString.capitalize("Unhide ^all"), this._filter.removeMessageURLFilter.bind(this._filter));
568 filterSubMenu.appendSeparator();
570 var hasFilters = false;
572 for (var url in this._filter.messageURLFilters) {
573 filterSubMenu.appendCheckboxItem(String.sprintf("%s (%d)", new WebInspector.ParsedURL(url).displayName, this._urlToMessageCount[url]), this._filter.removeMessageURLFilter.bind(this._filter, url), true);
574 hasFilters = true;
577 filterSubMenu.setEnabled(hasFilters || (consoleMessage && consoleMessage.url));
578 unhideAll.setEnabled(hasFilters);
580 contextMenu.appendSeparator();
581 contextMenu.appendAction("console.clear");
582 contextMenu.appendItem(WebInspector.UIString("Save as..."), this._saveConsole.bind(this));
584 var request = consoleMessage ? consoleMessage.request : null;
585 if (request && request.resourceType() === WebInspector.resourceTypes.XHR) {
586 contextMenu.appendSeparator();
587 contextMenu.appendItem(WebInspector.UIString("Replay XHR"), request.replayXHR.bind(request));
590 contextMenu.show();
593 _saveConsole: function()
595 var filename = String.sprintf("%s-%d.log", WebInspector.targetManager.inspectedPageDomain(), Date.now());
596 var stream = new WebInspector.FileOutputStream();
598 var progressIndicator = new WebInspector.ProgressIndicator();
599 progressIndicator.setTitle(WebInspector.UIString("Writing file…"));
600 progressIndicator.setTotalWork(this.itemCount());
602 /** @const */
603 var chunkSize = 350;
604 var messageIndex = 0;
606 stream.open(filename, openCallback.bind(this));
609 * @param {boolean} accepted
610 * @this {WebInspector.ConsoleView}
612 function openCallback(accepted)
614 if (!accepted)
615 return;
616 this._progressToolbarItem.element.appendChild(progressIndicator.element);
617 writeNextChunk.call(this, stream);
621 * @param {!WebInspector.OutputStream} stream
622 * @param {string=} error
623 * @this {WebInspector.ConsoleView}
625 function writeNextChunk(stream, error)
627 if (messageIndex >= this.itemCount() || error) {
628 stream.close();
629 progressIndicator.done();
630 return;
632 var lines = [];
633 for (var i = 0; i < chunkSize && i + messageIndex < this.itemCount(); ++i) {
634 var message = this.itemElement(messageIndex + i);
635 lines.push(message.searchableElement().deepTextContent());
637 messageIndex += i;
638 stream.write(lines.join("\n") + "\n", writeNextChunk.bind(this));
639 progressIndicator.setWorked(messageIndex);
645 * @param {!WebInspector.ConsoleViewMessage} lastMessage
646 * @param {?WebInspector.ConsoleViewMessage=} viewMessage
647 * @return {boolean}
649 _tryToCollapseMessages: function(lastMessage, viewMessage)
651 if (!WebInspector.moduleSetting("consoleTimestampsEnabled").get() && viewMessage && !lastMessage.consoleMessage().isGroupMessage() && lastMessage.consoleMessage().isEqual(viewMessage.consoleMessage())) {
652 viewMessage.incrementRepeatCount();
653 return true;
656 return false;
659 _updateMessageList: function()
661 this._topGroup = WebInspector.ConsoleGroup.createTopGroup();
662 this._currentGroup = this._topGroup;
663 this._regexMatchRanges = [];
664 this._hiddenByFilterCount = 0;
665 for (var i = 0; i < this._visibleViewMessages.length; ++i) {
666 this._visibleViewMessages[i].resetCloseGroupDecorationCount();
667 this._visibleViewMessages[i].resetIncrementRepeatCount();
669 this._visibleViewMessages = [];
670 for (var i = 0; i < this._consoleMessages.length; ++i)
671 this._appendMessageToEnd(this._consoleMessages[i]);
672 this._updateFilterStatus();
673 this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length);
674 this._viewport.invalidate();
678 * @param {!WebInspector.Event} event
680 _monitoringXHREnabledSettingChanged: function(event)
682 var enabled = /** @type {boolean} */ (event.data);
683 WebInspector.targetManager.targets().forEach(function(target) {target.networkAgent().setMonitoringXHREnabled(enabled);});
687 * @param {!Event} event
689 _messagesClicked: function(event)
691 var targetElement = event.deepElementFromPoint();
692 if (!this._prompt.isCaretInsidePrompt() && (!targetElement || targetElement.isComponentSelectionCollapsed()))
693 this._prompt.moveCaretToEndOfPrompt();
694 var groupMessage = event.target.enclosingNodeOrSelfWithClass("console-group-title");
695 if (!groupMessage)
696 return;
697 var consoleGroupViewMessage = groupMessage.parentElement.message;
698 consoleGroupViewMessage.setCollapsed(!consoleGroupViewMessage.collapsed());
699 this._updateMessageList();
702 _registerShortcuts: function()
704 this._shortcuts = {};
706 var shortcut = WebInspector.KeyboardShortcut;
707 var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Console"));
709 var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
710 var keys = [shortcutL];
711 if (WebInspector.isMac()) {
712 var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta);
713 keys.unshift(shortcutK);
715 section.addAlternateKeys(keys, WebInspector.UIString("Clear console"));
717 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Tab), WebInspector.UIString("Autocomplete common prefix"));
718 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion"));
720 var shortcutU = shortcut.makeDescriptor("u", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
721 this._shortcuts[shortcutU.key] = this._clearPromptBackwards.bind(this);
722 section.addAlternateKeys([shortcutU], WebInspector.UIString("Clear console prompt"));
724 keys = [
725 shortcut.makeDescriptor(shortcut.Keys.Down),
726 shortcut.makeDescriptor(shortcut.Keys.Up)
728 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line"));
730 if (WebInspector.isMac()) {
731 keys = [
732 shortcut.makeDescriptor("N", shortcut.Modifiers.Alt),
733 shortcut.makeDescriptor("P", shortcut.Modifiers.Alt)
735 section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command"));
738 section.addKey(shortcut.makeDescriptor(shortcut.Keys.Enter), WebInspector.UIString("Execute command"));
741 _clearPromptBackwards: function()
743 this._prompt.setText("");
746 _promptKeyDown: function(event)
748 if (isEnterKey(event)) {
749 this._enterKeyPressed(event);
750 return;
753 var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
754 var handler = this._shortcuts[shortcut];
755 if (handler) {
756 handler();
757 event.preventDefault();
761 _enterKeyPressed: function(event)
763 if (event.altKey || event.ctrlKey || event.shiftKey)
764 return;
766 event.consume(true);
768 this._prompt.clearAutoComplete(true);
770 var str = this._prompt.text();
771 if (!str.length)
772 return;
773 this._appendCommand(str, true);
777 * @param {?WebInspector.RemoteObject} result
778 * @param {boolean} wasThrown
779 * @param {!WebInspector.ConsoleMessage} originatingConsoleMessage
780 * @param {?DebuggerAgent.ExceptionDetails=} exceptionDetails
782 _printResult: function(result, wasThrown, originatingConsoleMessage, exceptionDetails)
784 if (!result)
785 return;
787 var level = wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log;
788 var message;
789 if (!wasThrown)
790 message = new WebInspector.ConsoleMessage(result.target(), WebInspector.ConsoleMessage.MessageSource.JS, level, "", WebInspector.ConsoleMessage.MessageType.Result, undefined, undefined, undefined, undefined, [result]);
791 else
792 message = new WebInspector.ConsoleMessage(result.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, undefined, undefined, undefined, exceptionDetails.scriptId);
793 message.setOriginatingMessage(originatingConsoleMessage);
794 result.target().consoleModel.addMessage(message);
798 * @param {string} text
799 * @param {boolean} useCommandLineAPI
801 _appendCommand: function(text, useCommandLineAPI)
803 this._prompt.setText("");
804 var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
805 if (currentExecutionContext) {
806 WebInspector.ConsoleModel.evaluateCommandInConsole(currentExecutionContext, text, useCommandLineAPI);
807 if (WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().name === "console")
808 WebInspector.userMetrics.CommandEvaluatedInConsolePanel.record();
813 * @param {!WebInspector.Event} event
815 _commandEvaluated: function(event)
817 var data = /**{{result: ?WebInspector.RemoteObject, wasThrown: boolean, text: string, commandMessage: !WebInspector.ConsoleMessage}} */ (event.data);
818 this._prompt.pushHistoryItem(data.text);
819 this._consoleHistorySetting.set(this._prompt.historyData().slice(-WebInspector.ConsoleView.persistedHistorySize));
820 this._printResult(data.result, data.wasThrown, data.commandMessage, data.exceptionDetails);
824 * @override
825 * @return {!Array.<!Element>}
827 elementsToRestoreScrollPositionsFor: function()
829 return [this._messagesElement];
833 * @override
835 searchCanceled: function()
837 this._cleanupAfterSearch();
838 for (var i = 0; i < this._visibleViewMessages.length; ++i) {
839 var message = this._visibleViewMessages[i];
840 message.setSearchRegex(null);
842 this._currentMatchRangeIndex = -1;
843 this._regexMatchRanges = [];
844 delete this._searchRegex;
845 this._viewport.refresh();
849 * @override
850 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
851 * @param {boolean} shouldJump
852 * @param {boolean=} jumpBackwards
854 performSearch: function(searchConfig, shouldJump, jumpBackwards)
856 this.searchCanceled();
857 this._searchableView.updateSearchMatchesCount(0);
859 this._searchRegex = searchConfig.toSearchRegex(true);
861 this._regexMatchRanges = [];
862 this._currentMatchRangeIndex = -1;
864 if (shouldJump)
865 this._searchShouldJumpBackwards = !!jumpBackwards;
867 this._searchProgressIndicator = new WebInspector.ProgressIndicator();
868 this._searchProgressIndicator.setTitle(WebInspector.UIString("Searching…"));
869 this._searchProgressIndicator.setTotalWork(this._visibleViewMessages.length);
870 this._progressToolbarItem.element.appendChild(this._searchProgressIndicator.element);
872 this._innerSearch(0);
875 _cleanupAfterSearch: function()
877 delete this._searchShouldJumpBackwards;
878 if (this._innerSearchTimeoutId) {
879 clearTimeout(this._innerSearchTimeoutId);
880 delete this._innerSearchTimeoutId;
882 if (this._searchProgressIndicator) {
883 this._searchProgressIndicator.done();
884 delete this._searchProgressIndicator;
888 _searchFinishedForTests: function()
890 // This method is sniffed in tests.
894 * @param {number} index
896 _innerSearch: function(index)
898 delete this._innerSearchTimeoutId;
899 if (this._searchProgressIndicator.isCanceled()) {
900 this._cleanupAfterSearch();
901 return;
904 var startTime = Date.now();
905 for (; index < this._visibleViewMessages.length && Date.now() - startTime < 100; ++index)
906 this._searchMessage(index);
908 this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length);
909 if (typeof this._searchShouldJumpBackwards !== "undefined" && this._regexMatchRanges.length) {
910 this._jumpToMatch(this._searchShouldJumpBackwards ? -1 : 0);
911 delete this._searchShouldJumpBackwards;
914 if (index === this._visibleViewMessages.length) {
915 this._cleanupAfterSearch();
916 setTimeout(this._searchFinishedForTests.bind(this), 0);
917 return;
920 this._innerSearchTimeoutId = setTimeout(this._innerSearch.bind(this, index), 100);
921 this._searchProgressIndicator.setWorked(index);
925 * @param {number} index
927 _searchMessage: function(index)
929 var message = this._visibleViewMessages[index];
930 message.setSearchRegex(this._searchRegex);
931 for (var i = 0; i < message.searchCount(); ++i) {
932 this._regexMatchRanges.push({
933 messageIndex: index,
934 matchIndex: i
940 * @override
942 jumpToNextSearchResult: function()
944 this._jumpToMatch(this._currentMatchRangeIndex + 1);
948 * @override
950 jumpToPreviousSearchResult: function()
952 this._jumpToMatch(this._currentMatchRangeIndex - 1);
956 * @override
957 * @return {boolean}
959 supportsCaseSensitiveSearch: function()
961 return true;
965 * @override
966 * @return {boolean}
968 supportsRegexSearch: function()
970 return true;
974 * @param {number} index
976 _jumpToMatch: function(index)
978 if (!this._regexMatchRanges.length)
979 return;
981 var currentSearchResultClassName = "current-search-result";
983 var matchRange;
984 if (this._currentMatchRangeIndex >= 0) {
985 matchRange = this._regexMatchRanges[this._currentMatchRangeIndex];
986 var message = this._visibleViewMessages[matchRange.messageIndex];
987 message.searchHighlightNode(matchRange.matchIndex).classList.remove(currentSearchResultClassName);
990 index = mod(index, this._regexMatchRanges.length);
991 this._currentMatchRangeIndex = index;
992 this._searchableView.updateCurrentMatchIndex(index);
993 matchRange = this._regexMatchRanges[index];
994 var message = this._visibleViewMessages[matchRange.messageIndex];
995 var highlightNode = message.searchHighlightNode(matchRange.matchIndex);
996 highlightNode.classList.add(currentSearchResultClassName);
997 this._viewport.scrollItemIntoView(matchRange.messageIndex);
998 highlightNode.scrollIntoViewIfNeeded();
1001 __proto__: WebInspector.VBox.prototype
1005 * @constructor
1006 * @extends {WebInspector.Object}
1007 * @param {!WebInspector.ConsoleView} view
1009 WebInspector.ConsoleViewFilter = function(view)
1011 this._messageURLFiltersSetting = WebInspector.settings.createSetting("messageURLFilters", {});
1012 this._messageLevelFiltersSetting = WebInspector.settings.createSetting("messageLevelFilters", {});
1014 this._view = view;
1015 this._messageURLFilters = this._messageURLFiltersSetting.get();
1016 this._filterChanged = this.dispatchEventToListeners.bind(this, WebInspector.ConsoleViewFilter.Events.FilterChanged);
1019 WebInspector.ConsoleViewFilter.Events = {
1020 FilterChanged: "FilterChanged"
1023 WebInspector.ConsoleViewFilter.prototype = {
1024 addFilters: function(filterBar)
1026 this._textFilterUI = new WebInspector.TextFilterUI(true);
1027 this._textFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._textFilterChanged, this);
1028 filterBar.addFilter(this._textFilterUI);
1030 this._hideNetworkMessagesCheckbox = new WebInspector.CheckboxFilterUI("hide-network-messages", WebInspector.UIString("Hide network messages"), true, WebInspector.moduleSetting("hideNetworkMessages"));
1031 this._hideNetworkMessagesCheckbox.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
1032 filterBar.addFilter(this._hideNetworkMessagesCheckbox);
1034 var levels = [
1035 {name: WebInspector.ConsoleMessage.MessageLevel.Error, label: WebInspector.UIString("Errors")},
1036 {name: WebInspector.ConsoleMessage.MessageLevel.Warning, label: WebInspector.UIString("Warnings")},
1037 {name: WebInspector.ConsoleMessage.MessageLevel.Info, label: WebInspector.UIString("Info")},
1038 {name: WebInspector.ConsoleMessage.MessageLevel.Log, label: WebInspector.UIString("Logs")},
1039 {name: WebInspector.ConsoleMessage.MessageLevel.Debug, label: WebInspector.UIString("Debug")},
1040 {name: WebInspector.ConsoleMessage.MessageLevel.RevokedError, label: WebInspector.UIString("Handled")}
1042 this._levelFilterUI = new WebInspector.NamedBitSetFilterUI(levels, this._messageLevelFiltersSetting);
1043 this._levelFilterUI.addEventListener(WebInspector.FilterUI.Events.FilterChanged, this._filterChanged, this);
1044 filterBar.addFilter(this._levelFilterUI);
1047 _textFilterChanged: function(event)
1049 this._filterRegex = this._textFilterUI.regex();
1051 this._filterChanged();
1055 * @param {string} url
1057 addMessageURLFilter: function(url)
1059 this._messageURLFilters[url] = true;
1060 this._messageURLFiltersSetting.set(this._messageURLFilters);
1061 this._filterChanged();
1065 * @param {string} url
1067 removeMessageURLFilter: function(url)
1069 if (!url)
1070 this._messageURLFilters = {};
1071 else
1072 delete this._messageURLFilters[url];
1074 this._messageURLFiltersSetting.set(this._messageURLFilters);
1075 this._filterChanged();
1079 * @returns {!Object}
1081 get messageURLFilters()
1083 return this._messageURLFilters;
1087 * @param {!WebInspector.ConsoleViewMessage} viewMessage
1088 * @return {boolean}
1090 shouldBeVisible: function(viewMessage)
1092 var message = viewMessage.consoleMessage();
1093 var executionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
1094 if (!message.target())
1095 return true;
1097 if (!this._view._showAllMessagesCheckbox.checked() && executionContext) {
1098 if (message.target() !== executionContext.target())
1099 return false;
1100 if (message.executionContextId && message.executionContextId !== executionContext.id) {
1101 return false;
1105 if (WebInspector.moduleSetting("hideNetworkMessages").get() && viewMessage.consoleMessage().source === WebInspector.ConsoleMessage.MessageSource.Network)
1106 return false;
1108 if (viewMessage.consoleMessage().isGroupMessage())
1109 return true;
1111 if (message.type === WebInspector.ConsoleMessage.MessageType.Result || message.type === WebInspector.ConsoleMessage.MessageType.Command)
1112 return true;
1114 if (message.url && this._messageURLFilters[message.url])
1115 return false;
1117 if (message.level && !this._levelFilterUI.accept(message.level))
1118 return false;
1120 if (this._filterRegex) {
1121 this._filterRegex.lastIndex = 0;
1122 if (!viewMessage.matchesFilterRegex(this._filterRegex))
1123 return false;
1126 return true;
1129 reset: function()
1131 this._messageURLFilters = {};
1132 this._messageURLFiltersSetting.set(this._messageURLFilters);
1133 this._messageLevelFiltersSetting.set({});
1134 this._view._showAllMessagesCheckbox.inputElement.checked = true;
1135 WebInspector.moduleSetting("hideNetworkMessages").set(false);
1136 this._textFilterUI.setValue("");
1137 this._filterChanged();
1140 __proto__: WebInspector.Object.prototype
1145 * @constructor
1146 * @extends {WebInspector.ConsoleViewMessage}
1147 * @param {!WebInspector.ConsoleMessage} message
1148 * @param {!WebInspector.Linkifier} linkifier
1149 * @param {number} nestingLevel
1151 WebInspector.ConsoleCommand = function(message, linkifier, nestingLevel)
1153 WebInspector.ConsoleViewMessage.call(this, message, linkifier, nestingLevel);
1156 WebInspector.ConsoleCommand.prototype = {
1158 * @override
1159 * @return {!Element})
1161 searchableElement: function()
1163 return this.contentElement();
1167 * @override
1168 * @return {!Element}
1170 contentElement: function()
1172 if (!this._element) {
1173 this._element = createElementWithClass("div", "console-user-command");
1174 this._element.message = this;
1176 this._formattedCommand = createElementWithClass("span", "console-message-text source-code");
1177 this._formattedCommand.textContent = this.text;
1178 this._element.appendChild(this._formattedCommand);
1180 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true);
1181 javascriptSyntaxHighlighter.syntaxHighlightNode(this._formattedCommand).then(this._updateSearch.bind(this))
1183 return this._element;
1186 _updateSearch: function()
1188 this.setSearchRegex(this.searchRegex());
1191 __proto__: WebInspector.ConsoleViewMessage.prototype
1195 * @constructor
1196 * @extends {WebInspector.ConsoleViewMessage}
1197 * @param {!WebInspector.ConsoleMessage} message
1198 * @param {!WebInspector.Linkifier} linkifier
1199 * @param {number} nestingLevel
1201 WebInspector.ConsoleCommandResult = function(message, linkifier, nestingLevel)
1203 WebInspector.ConsoleViewMessage.call(this, message, linkifier, nestingLevel);
1206 WebInspector.ConsoleCommandResult.prototype = {
1208 * @override
1209 * @param {!WebInspector.RemoteObject} array
1210 * @return {boolean}
1212 useArrayPreviewInFormatter: function(array)
1214 return false;
1218 * @override
1219 * @return {!Element}
1221 contentElement: function()
1223 var element = WebInspector.ConsoleViewMessage.prototype.contentElement.call(this);
1224 element.classList.add("console-user-command-result");
1225 this.updateTimestamp(false);
1226 return element;
1229 __proto__: WebInspector.ConsoleViewMessage.prototype
1233 * @constructor
1234 * @param {?WebInspector.ConsoleGroup} parentGroup
1235 * @param {?WebInspector.ConsoleViewMessage} groupMessage
1237 WebInspector.ConsoleGroup = function(parentGroup, groupMessage)
1239 this._parentGroup = parentGroup;
1240 this._nestingLevel = parentGroup ? parentGroup.nestingLevel() + 1 : 0;
1241 this._messagesHidden = groupMessage && groupMessage.collapsed() || this._parentGroup && this._parentGroup.messagesHidden();
1245 * @return {!WebInspector.ConsoleGroup}
1247 WebInspector.ConsoleGroup.createTopGroup = function()
1249 return new WebInspector.ConsoleGroup(null, null);
1252 WebInspector.ConsoleGroup.prototype = {
1254 * @return {boolean}
1256 messagesHidden: function()
1258 return this._messagesHidden;
1262 * @return {number}
1264 nestingLevel: function()
1266 return this._nestingLevel;
1270 * @return {?WebInspector.ConsoleGroup}
1272 parentGroup: function()
1274 return this._parentGroup || this;
1279 * @constructor
1280 * @implements {WebInspector.ActionDelegate}
1282 WebInspector.ConsoleView.ActionDelegate = function()
1286 WebInspector.ConsoleView.ActionDelegate.prototype = {
1288 * @override
1289 * @param {!WebInspector.Context} context
1290 * @param {string} actionId
1292 handleAction: function(context, actionId)
1294 if (actionId === "console.show")
1295 WebInspector.console.show();
1296 else if (actionId === "console.clear")
1297 WebInspector.ConsoleModel.clearConsole();
1302 * @typedef {{messageIndex: number, matchIndex: number}}
1304 WebInspector.ConsoleView.RegexMatchRange;