/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import * as Bindings from '../bindings/bindings.js'; import * as BrowserSDK from '../browser_sdk/browser_sdk.js'; import * as Common from '../common/common.js'; import * as Components from '../components/components.js'; import * as Host from '../host/host.js'; import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as TextUtils from '../text_utils/text_utils.js'; import * as UI from '../ui/ui.js'; import {ConsoleContextSelector} from './ConsoleContextSelector.js'; import {ConsoleFilter, FilterType} from './ConsoleFilter.js'; import {ConsolePinPane} from './ConsolePinPane.js'; import {ConsolePrompt, Events as ConsolePromptEvents} from './ConsolePrompt.js'; import {ConsoleSidebar, Events} from './ConsoleSidebar.js'; import {ConsoleCommand, ConsoleCommandResult, ConsoleGroupViewMessage, ConsoleViewMessage, getMessageForElement, MaxLengthForLinks} from './ConsoleViewMessage.js'; // eslint-disable-line no-unused-vars import {ConsoleViewport, ConsoleViewportElement, ConsoleViewportProvider} from './ConsoleViewport.js'; // eslint-disable-line no-unused-vars /** @type {!ConsoleView} */ let consoleViewInstance; /** * @implements {UI.SearchableView.Searchable} * @implements {ConsoleViewportProvider} * @unrestricted */ export class ConsoleView extends UI.Widget.VBox { constructor() { super(); this.setMinimumSize(0, 35); this.registerRequiredCSS('console/consoleView.css'); this.registerRequiredCSS('object_ui/objectValue.css'); this._searchableView = new UI.SearchableView.SearchableView(this); this._searchableView.element.classList.add('console-searchable-view'); this._searchableView.setPlaceholder(Common.UIString.UIString('Find string in logs')); this._searchableView.setMinimalSearchQuerySize(0); this._sidebar = new ConsoleSidebar(); this._sidebar.addEventListener(Events.FilterSelected, this._onFilterChanged.bind(this)); this._isSidebarOpen = false; this._filter = new ConsoleViewFilter(this._onFilterChanged.bind(this)); this._consoleToolbarContainer = this.element.createChild('div', 'console-toolbar-container'); this._splitWidget = new UI.SplitWidget.SplitWidget( true /* isVertical */, false /* secondIsSidebar */, 'console.sidebar.width', 100); this._splitWidget.setMainWidget(this._searchableView); this._splitWidget.setSidebarWidget(this._sidebar); this._splitWidget.show(this.element); this._splitWidget.hideSidebar(); this._splitWidget.enableShowModeSaving(); this._isSidebarOpen = this._splitWidget.showMode() === UI.SplitWidget.ShowMode.Both; if (this._isSidebarOpen) { this._filter._levelMenuButton.setEnabled(false); } this._splitWidget.addEventListener(UI.SplitWidget.Events.ShowModeChanged, event => { this._isSidebarOpen = event.data === UI.SplitWidget.ShowMode.Both; this._filter._levelMenuButton.setEnabled(!this._isSidebarOpen); this._onFilterChanged(); }); this._contentsElement = this._searchableView.element; this.element.classList.add('console-view'); /** @type {!Array.} */ this._visibleViewMessages = []; this._hiddenByFilterCount = 0; /** @type {!Set} */ this._shouldBeHiddenCache = new Set(); /** @type {number} */ this._lastShownHiddenByFilterCount; /** @type {number} */ this._currentMatchRangeIndex; /** @type {?RegExp} */ this._searchRegex; /** @type {!Map>} */ this._groupableMessages = new Map(); /** @type {!Map} */ this._groupableMessageTitle = new Map(); /** @type {!Map} */ this._shortcuts = new Map(); /** * @type {!Array.} */ this._regexMatchRanges = []; this._consoleContextSelector = new ConsoleContextSelector(); this._filterStatusText = new UI.Toolbar.ToolbarText(); this._filterStatusText.element.classList.add('dimmed'); this._showSettingsPaneSetting = Common.Settings.Settings.instance().createSetting('consoleShowSettingsToolbar', false); this._showSettingsPaneButton = new UI.Toolbar.ToolbarSettingToggle( this._showSettingsPaneSetting, 'largeicon-settings-gear', Common.UIString.UIString('Console settings')); this._progressToolbarItem = new UI.Toolbar.ToolbarItem(document.createElement('div')); this._groupSimilarSetting = Common.Settings.Settings.instance().moduleSetting('consoleGroupSimilar'); this._groupSimilarSetting.addChangeListener(() => this._updateMessageList()); const groupSimilarToggle = new UI.Toolbar.ToolbarSettingCheckbox( this._groupSimilarSetting, Common.UIString.UIString('Group similar messages in console')); const toolbar = new UI.Toolbar.Toolbar('console-main-toolbar', this._consoleToolbarContainer); const rightToolbar = new UI.Toolbar.Toolbar('', this._consoleToolbarContainer); toolbar.appendToolbarItem(this._splitWidget.createShowHideSidebarButton(ls`console sidebar`)); toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton( /** @type {!UI.Action.Action }*/ (UI.ActionRegistry.ActionRegistry.instance().action('console.clear')))); toolbar.appendSeparator(); toolbar.appendToolbarItem(this._consoleContextSelector.toolbarItem()); toolbar.appendSeparator(); const liveExpressionButton = UI.Toolbar.Toolbar.createActionButton( /** @type {!UI.Action.Action }*/ (UI.ActionRegistry.ActionRegistry.instance().action('console.create-pin'))); toolbar.appendToolbarItem(liveExpressionButton); toolbar.appendSeparator(); toolbar.appendToolbarItem(this._filter._textFilterUI); toolbar.appendToolbarItem(this._filter._levelMenuButton); toolbar.appendToolbarItem(this._progressToolbarItem); rightToolbar.appendSeparator(); rightToolbar.appendToolbarItem(this._filterStatusText); rightToolbar.appendToolbarItem(this._showSettingsPaneButton); this._preserveLogCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( Common.Settings.Settings.instance().moduleSetting('preserveConsoleLog'), Common.UIString.UIString('Do not clear log on page reload / navigation'), Common.UIString.UIString('Preserve log')); this._hideNetworkMessagesCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( this._filter._hideNetworkMessagesSetting, this._filter._hideNetworkMessagesSetting.title(), Common.UIString.UIString('Hide network')); const filterByExecutionContextCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( this._filter._filterByExecutionContextSetting, Common.UIString.UIString('Only show messages from the current context (top, iframe, worker, extension)'), Common.UIString.UIString('Selected context only')); const monitoringXHREnabledSetting = Common.Settings.Settings.instance().moduleSetting('monitoringXHREnabled'); this._timestampsSetting = Common.Settings.Settings.instance().moduleSetting('consoleTimestampsEnabled'); this._consoleHistoryAutocompleteSetting = Common.Settings.Settings.instance().moduleSetting('consoleHistoryAutocomplete'); const settingsPane = new UI.Widget.HBox(); settingsPane.show(this._contentsElement); settingsPane.element.classList.add('console-settings-pane'); UI.ARIAUtils.setAccessibleName(settingsPane.element, ls`Console settings`); UI.ARIAUtils.markAsGroup(settingsPane.element); const settingsToolbarLeft = new UI.Toolbar.Toolbar('', settingsPane.element); settingsToolbarLeft.makeVertical(); settingsToolbarLeft.appendToolbarItem(this._hideNetworkMessagesCheckbox); settingsToolbarLeft.appendToolbarItem(this._preserveLogCheckbox); settingsToolbarLeft.appendToolbarItem(filterByExecutionContextCheckbox); settingsToolbarLeft.appendToolbarItem(groupSimilarToggle); const settingsToolbarRight = new UI.Toolbar.Toolbar('', settingsPane.element); settingsToolbarRight.makeVertical(); settingsToolbarRight.appendToolbarItem(new UI.Toolbar.ToolbarSettingCheckbox(monitoringXHREnabledSetting)); const eagerEvalCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( Common.Settings.Settings.instance().moduleSetting('consoleEagerEval'), ls`Eagerly evaluate text in the prompt`); settingsToolbarRight.appendToolbarItem(eagerEvalCheckbox); settingsToolbarRight.appendToolbarItem( new UI.Toolbar.ToolbarSettingCheckbox(this._consoleHistoryAutocompleteSetting)); const userGestureCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( Common.Settings.Settings.instance().moduleSetting('consoleUserActivationEval')); settingsToolbarRight.appendToolbarItem(userGestureCheckbox); if (!this._showSettingsPaneSetting.get()) { settingsPane.element.classList.add('hidden'); } this._showSettingsPaneSetting.addChangeListener( () => settingsPane.element.classList.toggle('hidden', !this._showSettingsPaneSetting.get())); this._pinPane = new ConsolePinPane(liveExpressionButton); this._pinPane.element.classList.add('console-view-pinpane'); this._pinPane.show(this._contentsElement); this._pinPane.element.addEventListener('keydown', event => { if ((event.key === 'Enter' && UI.KeyboardShortcut.KeyboardShortcut.eventHasCtrlOrMeta(/** @type {!KeyboardEvent} */ (event))) || event.keyCode === UI.KeyboardShortcut.Keys.Esc.code) { this._prompt.focus(); event.consume(); } }); this._viewport = new ConsoleViewport(this); this._viewport.setStickToBottom(true); this._viewport.contentElement().classList.add('console-group', 'console-group-messages'); this._contentsElement.appendChild(this._viewport.element); this._messagesElement = this._viewport.element; this._messagesElement.id = 'console-messages'; this._messagesElement.classList.add('monospace'); this._messagesElement.addEventListener('click', this._messagesClicked.bind(this), false); this._messagesElement.addEventListener('paste', this._messagesPasted.bind(this), true); this._messagesElement.addEventListener('clipboard-paste', this._messagesPasted.bind(this), true); UI.ARIAUtils.markAsGroup(this._messagesElement); this._viewportThrottler = new Common.Throttler.Throttler(50); this._pendingBatchResize = false; /** * @param {!Common.EventTarget.EventTargetEvent} e */ this._onMessageResizedBound = e => { this._onMessageResized(e); }; this._topGroup = ConsoleGroup.createTopGroup(); this._currentGroup = this._topGroup; this._promptElement = this._messagesElement.createChild('div', 'source-code'); this._promptElement.id = 'console-prompt'; // FIXME: This is a workaround for the selection machinery bug. See crbug.com/410899 const selectAllFixer = this._messagesElement.createChild('div', 'console-view-fix-select-all'); selectAllFixer.textContent = '.'; UI.ARIAUtils.markAsHidden(selectAllFixer); this._registerShortcuts(); this._messagesElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), false); this._linkifier = new Components.Linkifier.Linkifier(MaxLengthForLinks); /** @type {!Array.} */ this._consoleMessages = []; this._viewMessageSymbol = Symbol('viewMessage'); this._consoleHistorySetting = Common.Settings.Settings.instance().createLocalSetting('consoleHistory', []); this._prompt = new ConsolePrompt(); this._prompt.show(this._promptElement); this._prompt.element.addEventListener('keydown', this._promptKeyDown.bind(this), true); this._prompt.addEventListener(ConsolePromptEvents.TextChanged, this._promptTextChanged, this); this._messagesElement.addEventListener('keydown', this._messagesKeyDown.bind(this), false); this._prompt.element.addEventListener('focusin', () => { if (this._isScrolledToBottom()) { this._viewport.setStickToBottom(true); } }); this._consoleHistoryAutocompleteSetting.addChangeListener(this._consoleHistoryAutocompleteChanged, this); const historyData = this._consoleHistorySetting.get(); this._prompt.history().setHistoryData(historyData); this._consoleHistoryAutocompleteChanged(); this._updateFilterStatus(); this._timestampsSetting.addChangeListener(this._consoleTimestampsSettingChanged, this); this._registerWithMessageSink(); UI.Context.Context.instance().addFlavorChangeListener( SDK.RuntimeModel.ExecutionContext, this._executionContextChanged, this); this._messagesElement.addEventListener( 'mousedown', /** @param {!Event} event */ event => this._updateStickToBottomOnPointerDown(/** @type {!MouseEvent} */ (event).button === 2), false); this._messagesElement.addEventListener('mouseup', this._updateStickToBottomOnPointerUp.bind(this), false); this._messagesElement.addEventListener('mouseleave', this._updateStickToBottomOnPointerUp.bind(this), false); this._messagesElement.addEventListener('wheel', this._updateStickToBottomOnWheel.bind(this), false); this._messagesElement.addEventListener( 'touchstart', this._updateStickToBottomOnPointerDown.bind(this, false), false); this._messagesElement.addEventListener('touchend', this._updateStickToBottomOnPointerUp.bind(this), false); this._messagesElement.addEventListener('touchcancel', this._updateStickToBottomOnPointerUp.bind(this), false); SDK.ConsoleModel.ConsoleModel.instance().addEventListener( SDK.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); SDK.ConsoleModel.ConsoleModel.instance().addEventListener( SDK.ConsoleModel.Events.MessageAdded, this._onConsoleMessageAdded, this); SDK.ConsoleModel.ConsoleModel.instance().addEventListener( SDK.ConsoleModel.Events.MessageUpdated, this._onConsoleMessageUpdated, this); SDK.ConsoleModel.ConsoleModel.instance().addEventListener( SDK.ConsoleModel.Events.CommandEvaluated, this._commandEvaluated, this); SDK.ConsoleModel.ConsoleModel.instance().messages().forEach(this._addConsoleMessage, this); SDK.SDKModel.TargetManager.instance().addModelListener( SDK.RuntimeModel.RuntimeModel, SDK.RuntimeModel.Events.ExecutionContextCreated, this._executionContextCreated, this); const issuesManager = BrowserSDK.IssuesManager.IssuesManager.instance(); issuesManager.addEventListener( BrowserSDK.IssuesManager.Events.IssuesCountUpdated, this._onIssuesCountChanged.bind(this)); if (issuesManager.numberOfIssues()) { this._onIssuesCountChanged(); } } _onIssuesCountChanged() { if (BrowserSDK.IssuesManager.IssuesManager.instance().numberOfIssues() === 0) { if (this._issueBarDiv) { this._issueBarDiv.element().remove(); this._issueBarDiv = null; this._scheduleViewportRefresh(); } } else if (!this._issueBarDiv) { const issueBarAction = /** @type {!UI.Infobar.InfobarAction} */ ({ text: ls`View issues`, highlight: false, delegate: () => { Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.ConsoleInfoBar); UI.ViewManager.ViewManager.instance().showView('issues-pane'); }, dismiss: false, }); const issueBar = new UI.Infobar.Infobar( UI.Infobar.Type.Issue, ls`Some messages have been moved to the Issues panel.`, [issueBarAction]); issueBar.element.tabIndex = -1; issueBar.element.classList.add('console-message-wrapper'); // This is a fake {ConsoleViewportElement} so the issue banner can be inserted into the {ConsoleViewport}. this._issueBarDiv = { willHide() { this._cachedIssueBarHeight = issueBar.element.offsetHeight; }, wasShown() {}, element: () => issueBar.element, focusLastChildOrSelf: () => issueBar.element.focus(), fastHeight() { return this._cachedIssueBarHeight || 37; }, toExportString: () => ls`Some messages have been moved to the Issues panel.`, _cachedIssueBarHeight: 0 }; this._scheduleViewportRefresh(); } } /** * @return {!ConsoleView} */ static instance() { if (!consoleViewInstance) { consoleViewInstance = new ConsoleView(); } return consoleViewInstance; } static clearConsole() { const consoleView = ConsoleView.instance(); if (consoleView._issueBarDiv) { consoleView._issueBarDiv.element().remove(); consoleView._issueBarDiv = null; consoleView._scheduleViewportRefresh(); } SDK.ConsoleModel.ConsoleModel.instance().requestClearMessages(); } _onFilterChanged() { this._filter._currentFilter.levelsMask = this._isSidebarOpen ? ConsoleFilter.allLevelsFilterValue() : this._filter._messageLevelFiltersSetting.get(); this._cancelBuildHiddenCache(); if (this._immediatelyFilterMessagesForTest) { for (const viewMessage of this._consoleMessages) { this._computeShouldMessageBeVisible(viewMessage); } this._updateMessageList(); return; } this._buildHiddenCache(0, this._consoleMessages.slice()); } _setImmediatelyFilterMessagesForTest() { this._immediatelyFilterMessagesForTest = true; } /** * @return {!UI.SearchableView.SearchableView} */ searchableView() { return this._searchableView; } _clearHistory() { this._consoleHistorySetting.set([]); this._prompt.history().setHistoryData([]); } _consoleHistoryAutocompleteChanged() { this._prompt.setAddCompletionsFromHistory(this._consoleHistoryAutocompleteSetting.get()); } /** * @override * @return {number} */ itemCount() { if (this._issueBarDiv) { return this._visibleViewMessages.length + 1; } return this._visibleViewMessages.length; } /** * @override * @param {number} index * @return {?ConsoleViewportElement} */ itemElement(index) { const issueBar = this._issueBarDiv; if (issueBar) { if (index === 0) { return /** @type {!ConsoleViewportElement} */ (issueBar); } return this._visibleViewMessages[index - 1]; } return this._visibleViewMessages[index]; } /** * @override * @param {number} index * @return {number} */ fastHeight(index) { const issueBar = this._issueBarDiv; if (issueBar) { if (index === 0) { return issueBar.fastHeight() || 37; } return this._visibleViewMessages[index - 1].fastHeight(); } return this._visibleViewMessages[index].fastHeight(); } /** * @override * @return {number} */ minimumRowHeight() { return 16; } _registerWithMessageSink() { Common.Console.Console.instance().messages().forEach(this._addSinkMessage, this); Common.Console.Console.instance().addEventListener(Common.Console.Events.MessageAdded, messageAdded, this); /** * @param {!Common.EventTarget.EventTargetEvent} event * @this {ConsoleView} */ function messageAdded(event) { this._addSinkMessage(/** @type {!Common.Console.Message} */ (event.data)); } } /** * @param {!Common.Console.Message} message */ _addSinkMessage(message) { let level = SDK.ConsoleModel.MessageLevel.Verbose; switch (message.level) { case Common.Console.MessageLevel.Info: level = SDK.ConsoleModel.MessageLevel.Info; break; case Common.Console.MessageLevel.Error: level = SDK.ConsoleModel.MessageLevel.Error; break; case Common.Console.MessageLevel.Warning: level = SDK.ConsoleModel.MessageLevel.Warning; break; } const consoleMessage = new SDK.ConsoleModel.ConsoleMessage( null, SDK.ConsoleModel.MessageSource.Other, level, message.text, SDK.ConsoleModel.MessageType.System, undefined, undefined, undefined, undefined, undefined, message.timestamp); this._addConsoleMessage(consoleMessage); } _consoleTimestampsSettingChanged() { this._updateMessageList(); this._consoleMessages.forEach(viewMessage => viewMessage.updateTimestamp()); this._groupableMessageTitle.forEach(viewMessage => viewMessage.updateTimestamp()); } _executionContextChanged() { this._prompt.clearAutocomplete(); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _executionContextCreated(event) { const executionContext = event.data; if (!executionContext.frameId) { return; } const oldLength = this._consoleMessages.length; this._consoleMessages = this._consoleMessages.filter(viewMessage => { const consoleMessage = viewMessage.consoleMessage(); // If a message from the execution context reported already exists, remove // it, as pre-existing messages from the execution context will be resent. if (consoleMessage.frameId && consoleMessage.executionContextId && consoleMessage.executionContextId === executionContext.id && consoleMessage.frameId === executionContext.frameId) { return false; } return true; }); const messageRemoved = this._consoleMessages.length < oldLength; if (messageRemoved) { this._updateMessageList(); } } /** * @override */ willHide() { this._hidePromptSuggestBox(); } /** * @override */ wasShown() { this._viewport.refresh(); } /** * @override */ focus() { if (this._viewport.hasVirtualSelection()) { /** @type {!HTMLElement} */ (this._viewport.contentElement()).focus(); } else { this._focusPrompt(); } } _focusPrompt() { if (!this._prompt.hasFocus()) { const oldStickToBottom = this._viewport.stickToBottom(); const oldScrollTop = this._viewport.element.scrollTop; this._prompt.focus(); this._viewport.setStickToBottom(oldStickToBottom); this._viewport.element.scrollTop = oldScrollTop; } } /** * @override */ restoreScrollPositions() { if (this._viewport.stickToBottom()) { this._immediatelyScrollToBottom(); } else { super.restoreScrollPositions(); } } /** * @override */ onResize() { this._scheduleViewportRefresh(); this._hidePromptSuggestBox(); if (this._viewport.stickToBottom()) { this._immediatelyScrollToBottom(); } for (let i = 0; i < this._visibleViewMessages.length; ++i) { this._visibleViewMessages[i].onResize(); } } _hidePromptSuggestBox() { this._prompt.clearAutocomplete(); } /** * @return {!Promise.} */ _invalidateViewport() { if (this._muteViewportUpdates) { this._maybeDirtyWhileMuted = true; return Promise.resolve(); } if (this._needsFullUpdate) { this._updateMessageList(); delete this._needsFullUpdate; } else { this._viewport.invalidate(); } return Promise.resolve(); } _scheduleViewportRefresh() { if (this._muteViewportUpdates) { this._maybeDirtyWhileMuted = true; this._scheduleViewportRefreshForTest(true); return; } this._scheduleViewportRefreshForTest(false); this._scheduledRefreshPromiseForTest = this._viewportThrottler.schedule(this._invalidateViewport.bind(this)); } /** * @param {boolean} muted */ _scheduleViewportRefreshForTest(muted) { // This functions is sniffed in tests. } _immediatelyScrollToBottom() { // This will scroll viewport and trigger its refresh. this._viewport.setStickToBottom(true); this._promptElement.scrollIntoView(true); } _updateFilterStatus() { if (this._hiddenByFilterCount === this._lastShownHiddenByFilterCount) { return; } this._filterStatusText.setText(ls`${this._hiddenByFilterCount} hidden`); this._filterStatusText.setVisible(!!this._hiddenByFilterCount); this._lastShownHiddenByFilterCount = this._hiddenByFilterCount; } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _onConsoleMessageAdded(event) { const message = /** @type {!SDK.ConsoleModel.ConsoleMessage} */ (event.data); this._addConsoleMessage(message); } /** * @param {!SDK.ConsoleModel.ConsoleMessage} message */ _addConsoleMessage(message) { const viewMessage = this._createViewMessage(message); consoleMessageToViewMessage.set(message, viewMessage); if (message.type === SDK.ConsoleModel.MessageType.Command || message.type === SDK.ConsoleModel.MessageType.Result) { const lastMessage = this._consoleMessages.peekLast(); const newTimestamp = lastMessage && messagesSortedBySymbol.get(lastMessage) || 0; messagesSortedBySymbol.set(viewMessage, newTimestamp); } else { messagesSortedBySymbol.set(viewMessage, viewMessage.consoleMessage().timestamp); } let insertAt; if (!this._consoleMessages.length || timeComparator(viewMessage, this._consoleMessages[this._consoleMessages.length - 1]) > 0) { insertAt = this._consoleMessages.length; } else { insertAt = this._consoleMessages.upperBound(viewMessage, timeComparator); } const insertedInMiddle = insertAt < this._consoleMessages.length; this._consoleMessages.splice(insertAt, 0, viewMessage); this._filter.onMessageAdded(message); this._sidebar.onMessageAdded(viewMessage); // If we already have similar messages, go slow path. let shouldGoIntoGroup = false; const shouldGroupSimilar = this._groupSimilarSetting.get(); if (message.isGroupable()) { const groupKey = viewMessage.groupKey(); shouldGoIntoGroup = shouldGroupSimilar && this._groupableMessages.has(groupKey); let list = this._groupableMessages.get(groupKey); if (!list) { list = []; this._groupableMessages.set(groupKey, list); } list.push(viewMessage); } this._computeShouldMessageBeVisible(viewMessage); if (!shouldGoIntoGroup && !insertedInMiddle) { this._appendMessageToEnd( viewMessage, !shouldGroupSimilar /* crbug.com/1082963: prevent collapse of same messages when "Group similar" is false */); this._updateFilterStatus(); this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length); } else { this._needsFullUpdate = true; } this._scheduleViewportRefresh(); this._consoleMessageAddedForTest(viewMessage); /** * @param {!ConsoleViewMessage} viewMessage1 * @param {!ConsoleViewMessage} viewMessage2 */ function timeComparator(viewMessage1, viewMessage2) { return (messagesSortedBySymbol.get(viewMessage1) || 0) - (messagesSortedBySymbol.get(viewMessage2) || 0); } } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _onConsoleMessageUpdated(event) { const message = /** @type {!SDK.ConsoleModel.ConsoleMessage} */ (event.data); const viewMessage = consoleMessageToViewMessage.get(message); if (viewMessage) { viewMessage.updateMessageElement(); this._computeShouldMessageBeVisible(viewMessage); this._updateMessageList(); } } /** * @param {!ConsoleViewMessage} viewMessage */ _consoleMessageAddedForTest(viewMessage) { } /** * @param {!ConsoleViewMessage} viewMessage * @return {boolean} */ _shouldMessageBeVisible(viewMessage) { return !this._shouldBeHiddenCache.has(viewMessage); } /** * @param {!ConsoleViewMessage} viewMessage */ _computeShouldMessageBeVisible(viewMessage) { if (this._filter.shouldBeVisible(viewMessage) && (!this._isSidebarOpen || this._sidebar.shouldBeVisible(viewMessage))) { this._shouldBeHiddenCache.delete(viewMessage); } else { this._shouldBeHiddenCache.add(viewMessage); } } /** * @param {!ConsoleViewMessage} viewMessage * @param {boolean=} preventCollapse */ _appendMessageToEnd(viewMessage, preventCollapse) { if (!this._shouldMessageBeVisible(viewMessage)) { this._hiddenByFilterCount++; return; } if (!preventCollapse && this._tryToCollapseMessages(viewMessage, this._visibleViewMessages.peekLast())) { return; } const lastMessage = this._visibleViewMessages.peekLast(); if (viewMessage.consoleMessage().type === SDK.ConsoleModel.MessageType.EndGroup) { if (lastMessage && !this._currentGroup.messagesHidden()) { lastMessage.incrementCloseGroupDecorationCount(); } this._currentGroup = this._currentGroup.parentGroup() || this._currentGroup; return; } if (!this._currentGroup.messagesHidden()) { const originatingMessage = viewMessage.consoleMessage().originatingMessage(); if (lastMessage && originatingMessage && lastMessage.consoleMessage() === originatingMessage) { viewMessage.toMessageElement().classList.add('console-adjacent-user-command-result'); } this._visibleViewMessages.push(viewMessage); this._searchMessage(this._visibleViewMessages.length - 1); } if (viewMessage.consoleMessage().isGroupStartMessage()) { this._currentGroup = new ConsoleGroup(this._currentGroup, /** @type {!ConsoleGroupViewMessage} */ (viewMessage)); } this._messageAppendedForTests(); } _messageAppendedForTests() { // This method is sniffed in tests. } /** * @param {!SDK.ConsoleModel.ConsoleMessage} message * @return {!ConsoleViewMessage} */ _createViewMessage(message) { const nestingLevel = this._currentGroup.nestingLevel(); switch (message.type) { case SDK.ConsoleModel.MessageType.Command: return new ConsoleCommand(message, this._linkifier, nestingLevel, this._onMessageResizedBound); case SDK.ConsoleModel.MessageType.Result: return new ConsoleCommandResult(message, this._linkifier, nestingLevel, this._onMessageResizedBound); case SDK.ConsoleModel.MessageType.StartGroupCollapsed: case SDK.ConsoleModel.MessageType.StartGroup: return new ConsoleGroupViewMessage( message, this._linkifier, nestingLevel, this._updateMessageList.bind(this), this._onMessageResizedBound); default: return new ConsoleViewMessage(message, this._linkifier, nestingLevel, this._onMessageResizedBound); } } /** * @param {!Common.EventTarget.EventTargetEvent} event * @return {!Promise} */ async _onMessageResized(event) { const treeElement = /** @type {!UI.TreeOutline.TreeElement} */ (event.data); if (this._pendingBatchResize || !treeElement.treeOutline) { return; } this._pendingBatchResize = true; await Promise.resolve(); const treeOutlineElement = treeElement.treeOutline.element; this._viewport.setStickToBottom(this._isScrolledToBottom()); // Scroll, in case mutations moved the element below the visible area. if (treeOutlineElement.offsetHeight <= this._messagesElement.offsetHeight) { treeOutlineElement.scrollIntoViewIfNeeded(); } this._pendingBatchResize = false; } _consoleCleared() { const hadFocus = this._viewport.element.hasFocus(); this._cancelBuildHiddenCache(); this._currentMatchRangeIndex = -1; this._consoleMessages = []; this._groupableMessages.clear(); this._groupableMessageTitle.clear(); this._sidebar.clear(); this._updateMessageList(); this._hidePromptSuggestBox(); this._viewport.setStickToBottom(true); this._linkifier.reset(); this._filter.clear(); if (hadFocus) { this._prompt.focus(); } UI.ARIAUtils.alert(ls`Console cleared`, this._viewport.element); } /** * @param {!Event} event */ _handleContextMenuEvent(event) { const contextMenu = new UI.ContextMenu.ContextMenu(event); const eventTarget = /** @type {!Node} */ (event.target); if (eventTarget.isSelfOrDescendant(this._promptElement)) { contextMenu.show(); return; } const sourceElement = eventTarget.enclosingNodeOrSelfWithClass('console-message-wrapper'); const consoleViewMessage = sourceElement && getMessageForElement(sourceElement); const consoleMessage = consoleViewMessage ? consoleViewMessage.consoleMessage() : null; if (consoleMessage && consoleMessage.url) { const menuTitle = ls`Hide messages from ${new Common.ParsedURL.ParsedURL(consoleMessage.url).displayName}`; contextMenu.headerSection().appendItem( menuTitle, this._filter.addMessageURLFilter.bind(this._filter, consoleMessage.url)); } contextMenu.defaultSection().appendAction('console.clear'); contextMenu.defaultSection().appendAction('console.clear.history'); contextMenu.saveSection().appendItem(Common.UIString.UIString('Save as...'), this._saveConsole.bind(this)); if (this.element.hasSelection()) { contextMenu.clipboardSection().appendItem( Common.UIString.UIString('Copy visible styled selection'), this._viewport.copyWithStyles.bind(this._viewport)); } if (consoleMessage) { const request = SDK.NetworkLog.NetworkLog.requestForConsoleMessage(consoleMessage); if (request && SDK.NetworkManager.NetworkManager.canReplayRequest(request)) { contextMenu.debugSection().appendItem( ls`Replay XHR`, SDK.NetworkManager.NetworkManager.replayRequest.bind(null, request)); } } contextMenu.show(); } async _saveConsole() { const url = /** @type {!SDK.SDKModel.Target} */ (SDK.SDKModel.TargetManager.instance().mainTarget()).inspectedURL(); const parsedURL = Common.ParsedURL.ParsedURL.fromString(url); const filename = Platform.StringUtilities.sprintf('%s-%d.log', parsedURL ? parsedURL.host : 'console', Date.now()); const stream = new Bindings.FileUtils.FileOutputStream(); const progressIndicator = new UI.ProgressIndicator.ProgressIndicator(); progressIndicator.setTitle(Common.UIString.UIString('Writing file…')); progressIndicator.setTotalWork(this.itemCount()); /** @const */ const chunkSize = 350; if (!await stream.open(filename)) { return; } this._progressToolbarItem.element.appendChild(progressIndicator.element); let messageIndex = 0; while (messageIndex < this.itemCount() && !progressIndicator.isCanceled()) { const messageContents = []; let i; for (i = 0; i < chunkSize && i + messageIndex < this.itemCount(); ++i) { const message = /** @type {!ConsoleViewMessage} */ (this.itemElement(messageIndex + i)); messageContents.push(message.toExportString()); } messageIndex += i; await stream.write(messageContents.join('\n') + '\n'); progressIndicator.setWorked(messageIndex); } stream.close(); progressIndicator.done(); } /** * @param {!ConsoleViewMessage} viewMessage * @param {!ConsoleViewMessage=} lastMessage * @return {boolean} */ _tryToCollapseMessages(viewMessage, lastMessage) { const timestampsShown = this._timestampsSetting.get(); if (!timestampsShown && lastMessage && !viewMessage.consoleMessage().isGroupMessage() && viewMessage.consoleMessage().type !== SDK.ConsoleModel.MessageType.Command && viewMessage.consoleMessage().type !== SDK.ConsoleModel.MessageType.Result && viewMessage.consoleMessage().isEqual(lastMessage.consoleMessage())) { lastMessage.incrementRepeatCount(); if (viewMessage.isLastInSimilarGroup()) { lastMessage.setInSimilarGroup(true, true); } return true; } return false; } /** * @param {number} startIndex * @param {!Array} viewMessages */ _buildHiddenCache(startIndex, viewMessages) { const startTime = Date.now(); let i; for (i = startIndex; i < viewMessages.length; ++i) { this._computeShouldMessageBeVisible(viewMessages[i]); if (i % 10 === 0 && Date.now() - startTime > 12) { break; } } if (i === viewMessages.length) { this._updateMessageList(); return; } this._buildHiddenCacheTimeout = this.element.window().requestAnimationFrame(this._buildHiddenCache.bind(this, i, viewMessages)); } _cancelBuildHiddenCache() { this._shouldBeHiddenCache.clear(); if (this._buildHiddenCacheTimeout) { this.element.window().cancelAnimationFrame(this._buildHiddenCacheTimeout); delete this._buildHiddenCacheTimeout; } } _updateMessageList() { this._topGroup = ConsoleGroup.createTopGroup(); this._currentGroup = this._topGroup; this._regexMatchRanges = []; this._hiddenByFilterCount = 0; for (let i = 0; i < this._visibleViewMessages.length; ++i) { this._visibleViewMessages[i].resetCloseGroupDecorationCount(); this._visibleViewMessages[i].resetIncrementRepeatCount(); } this._visibleViewMessages = []; if (this._groupSimilarSetting.get()) { this._addGroupableMessagesToEnd(); } else { for (let i = 0; i < this._consoleMessages.length; ++i) { this._consoleMessages[i].setInSimilarGroup(false); this._appendMessageToEnd( this._consoleMessages[i], true /* crbug.com/1082963: prevent collapse of same messages when "Group similar" is false */); } } this._updateFilterStatus(); this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length); this._viewport.invalidate(); } _addGroupableMessagesToEnd() { /** @type {!Set<(!SDK.ConsoleModel.ConsoleMessage|!ConsoleViewMessage)>} */ const alreadyAdded = new Set(); /** @type {!Set} */ const processedGroupKeys = new Set(); for (let i = 0; i < this._consoleMessages.length; ++i) { const viewMessage = this._consoleMessages[i]; const message = viewMessage.consoleMessage(); if (alreadyAdded.has(message)) { continue; } if (!message.isGroupable()) { this._appendMessageToEnd(viewMessage); alreadyAdded.add(message); continue; } const key = viewMessage.groupKey(); const viewMessagesInGroup = this._groupableMessages.get(key); if (!viewMessagesInGroup || viewMessagesInGroup.length < 5) { viewMessage.setInSimilarGroup(false); this._appendMessageToEnd(viewMessage); alreadyAdded.add(message); continue; } if (processedGroupKeys.has(key)) { continue; } if (!viewMessagesInGroup.find(x => this._shouldMessageBeVisible(x))) { // Optimize for speed. Platform.SetUtilities.addAll(alreadyAdded, viewMessagesInGroup); processedGroupKeys.add(key); continue; } // Create artificial group start and end messages. let startGroupViewMessage = this._groupableMessageTitle.get(key); if (!startGroupViewMessage) { const startGroupMessage = new SDK.ConsoleModel.ConsoleMessage( null, message.source, message.level, viewMessage.groupTitle(), SDK.ConsoleModel.MessageType.StartGroupCollapsed); startGroupViewMessage = this._createViewMessage(startGroupMessage); this._groupableMessageTitle.set(key, startGroupViewMessage); } startGroupViewMessage.setRepeatCount(viewMessagesInGroup.length); this._appendMessageToEnd(startGroupViewMessage); for (const viewMessageInGroup of viewMessagesInGroup) { viewMessageInGroup.setInSimilarGroup(true, viewMessagesInGroup.peekLast() === viewMessageInGroup); this._appendMessageToEnd(viewMessageInGroup, true); alreadyAdded.add(viewMessageInGroup.consoleMessage()); } const endGroupMessage = new SDK.ConsoleModel.ConsoleMessage( null, message.source, message.level, message.messageText, SDK.ConsoleModel.MessageType.EndGroup); this._appendMessageToEnd(this._createViewMessage(endGroupMessage)); } } /** * @param {!Event} event */ _messagesClicked(event) { const target = /** @type {?Node} */ (event.target); // Do not focus prompt if messages have selection. if (!this._messagesElement.hasSelection()) { const clickedOutsideMessageList = target === this._messagesElement || this._prompt.belowEditorElement().isSelfOrAncestor(target); if (clickedOutsideMessageList) { this._prompt.moveCaretToEndOfPrompt(); this._focusPrompt(); } } } /** * @param {!Event} event */ _messagesKeyDown(event) { const keyEvent = /** @type {!KeyboardEvent} */ (event); const hasActionModifier = keyEvent.ctrlKey || keyEvent.altKey || keyEvent.metaKey; if (hasActionModifier || keyEvent.key.length !== 1 || UI.UIUtils.isEditing() || this._messagesElement.hasSelection()) { return; } this._prompt.moveCaretToEndOfPrompt(); this._focusPrompt(); } /** * @param {!Event} event */ _messagesPasted(event) { if (UI.UIUtils.isEditing()) { return; } this._prompt.focus(); } _registerShortcuts() { this._shortcuts.set( UI.KeyboardShortcut.KeyboardShortcut.makeKey('u', UI.KeyboardShortcut.Modifiers.Ctrl), this._clearPromptBackwards.bind(this)); } _clearPromptBackwards() { this._prompt.setText(''); } /** * @param {!Event} event */ _promptKeyDown(event) { const keyboardEvent = /** @type {!KeyboardEvent} */ (event); if (keyboardEvent.key === 'PageUp') { this._updateStickToBottomOnWheel(); return; } const shortcut = UI.KeyboardShortcut.KeyboardShortcut.makeKeyFromEvent(keyboardEvent); const handler = this._shortcuts.get(shortcut); if (handler) { handler(); keyboardEvent.preventDefault(); } } /** * @param {?SDK.RemoteObject.RemoteObject} result * @param {!SDK.ConsoleModel.ConsoleMessage} originatingConsoleMessage * @param {!Protocol.Runtime.ExceptionDetails=} exceptionDetails */ _printResult(result, originatingConsoleMessage, exceptionDetails) { if (!result) { return; } const level = !!exceptionDetails ? SDK.ConsoleModel.MessageLevel.Error : SDK.ConsoleModel.MessageLevel.Info; let message; if (!exceptionDetails) { message = new SDK.ConsoleModel.ConsoleMessage( result.runtimeModel(), SDK.ConsoleModel.MessageSource.JS, level, '', SDK.ConsoleModel.MessageType.Result, undefined, undefined, undefined, [result]); } else { message = SDK.ConsoleModel.ConsoleMessage.fromException( result.runtimeModel(), exceptionDetails, SDK.ConsoleModel.MessageType.Result, undefined, undefined); } message.setOriginatingMessage(originatingConsoleMessage); SDK.ConsoleModel.ConsoleModel.instance().addMessage(message); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _commandEvaluated(event) { const data = /** @type {{result: ?SDK.RemoteObject.RemoteObject, commandMessage: !SDK.ConsoleModel.ConsoleMessage, exceptionDetails: (!Protocol.Runtime.ExceptionDetails|undefined)}} */ (event.data); this._prompt.history().pushHistoryItem(data.commandMessage.messageText); this._consoleHistorySetting.set(this._prompt.history().historyData().slice(-persistedHistorySize)); this._printResult(data.result, data.commandMessage, data.exceptionDetails); } /** * @override * @return {!Array.} */ elementsToRestoreScrollPositionsFor() { return [this._messagesElement]; } /** * @override */ searchCanceled() { this._cleanupAfterSearch(); for (let i = 0; i < this._visibleViewMessages.length; ++i) { const message = this._visibleViewMessages[i]; message.setSearchRegex(null); } this._currentMatchRangeIndex = -1; this._regexMatchRanges = []; this._searchRegex = null; this._viewport.refresh(); } /** * @override * @param {!UI.SearchableView.SearchConfig} searchConfig * @param {boolean} shouldJump * @param {boolean=} jumpBackwards */ performSearch(searchConfig, shouldJump, jumpBackwards) { this.searchCanceled(); this._searchableView.updateSearchMatchesCount(0); this._searchRegex = searchConfig.toSearchRegex(true); this._regexMatchRanges = []; this._currentMatchRangeIndex = -1; if (shouldJump) { this._searchShouldJumpBackwards = !!jumpBackwards; } this._searchProgressIndicator = new UI.ProgressIndicator.ProgressIndicator(); this._searchProgressIndicator.setTitle(Common.UIString.UIString('Searching…')); this._searchProgressIndicator.setTotalWork(this._visibleViewMessages.length); this._progressToolbarItem.element.appendChild(this._searchProgressIndicator.element); this._innerSearch(0); } _cleanupAfterSearch() { delete this._searchShouldJumpBackwards; if (this._innerSearchTimeoutId) { clearTimeout(this._innerSearchTimeoutId); delete this._innerSearchTimeoutId; } if (this._searchProgressIndicator) { this._searchProgressIndicator.done(); delete this._searchProgressIndicator; } } _searchFinishedForTests() { // This method is sniffed in tests. } /** * @param {number} index */ _innerSearch(index) { delete this._innerSearchTimeoutId; if (this._searchProgressIndicator && this._searchProgressIndicator.isCanceled()) { this._cleanupAfterSearch(); return; } const startTime = Date.now(); for (; index < this._visibleViewMessages.length && Date.now() - startTime < 100; ++index) { this._searchMessage(index); } this._searchableView.updateSearchMatchesCount(this._regexMatchRanges.length); if (typeof this._searchShouldJumpBackwards !== 'undefined' && this._regexMatchRanges.length) { this._jumpToMatch(this._searchShouldJumpBackwards ? -1 : 0); delete this._searchShouldJumpBackwards; } if (index === this._visibleViewMessages.length) { this._cleanupAfterSearch(); setTimeout(this._searchFinishedForTests.bind(this), 0); return; } this._innerSearchTimeoutId = setTimeout(this._innerSearch.bind(this, index), 100); if (this._searchProgressIndicator) { this._searchProgressIndicator.setWorked(index); } } /** * @param {number} index */ _searchMessage(index) { const message = this._visibleViewMessages[index]; message.setSearchRegex(this._searchRegex); for (let i = 0; i < message.searchCount(); ++i) { this._regexMatchRanges.push({messageIndex: index, matchIndex: i}); } } /** * @override */ jumpToNextSearchResult() { this._jumpToMatch(this._currentMatchRangeIndex + 1); } /** * @override */ jumpToPreviousSearchResult() { this._jumpToMatch(this._currentMatchRangeIndex - 1); } /** * @override * @return {boolean} */ supportsCaseSensitiveSearch() { return true; } /** * @override * @return {boolean} */ supportsRegexSearch() { return true; } /** * @param {number} index */ _jumpToMatch(index) { if (!this._regexMatchRanges.length) { return; } let matchRange; if (this._currentMatchRangeIndex >= 0) { matchRange = this._regexMatchRanges[this._currentMatchRangeIndex]; const message = this._visibleViewMessages[matchRange.messageIndex]; message.searchHighlightNode(matchRange.matchIndex) .classList.remove(UI.UIUtils.highlightedCurrentSearchResultClassName); } index = Platform.NumberUtilities.mod(index, this._regexMatchRanges.length); this._currentMatchRangeIndex = index; this._searchableView.updateCurrentMatchIndex(index); matchRange = this._regexMatchRanges[index]; const message = this._visibleViewMessages[matchRange.messageIndex]; const highlightNode = message.searchHighlightNode(matchRange.matchIndex); highlightNode.classList.add(UI.UIUtils.highlightedCurrentSearchResultClassName); const notifyOffset = this._issueBarDiv ? 1 : 0; this._viewport.scrollItemIntoView(matchRange.messageIndex + notifyOffset); highlightNode.scrollIntoViewIfNeeded(); } /** * @param {boolean=} isRightClick */ _updateStickToBottomOnPointerDown(isRightClick) { this._muteViewportUpdates = !isRightClick; this._viewport.setStickToBottom(false); if (this._waitForScrollTimeout) { clearTimeout(this._waitForScrollTimeout); delete this._waitForScrollTimeout; } } _updateStickToBottomOnPointerUp() { if (!this._muteViewportUpdates) { return; } // Delay querying isScrolledToBottom to give time for smooth scroll // events to arrive. The value for the longest timeout duration is // retrieved from crbug.com/575409. this._waitForScrollTimeout = setTimeout(updateViewportState.bind(this), 200); /** * @this {!ConsoleView} */ function updateViewportState() { this._muteViewportUpdates = false; if (this.isShowing()) { this._viewport.setStickToBottom(this._isScrolledToBottom()); } if (this._maybeDirtyWhileMuted) { this._scheduleViewportRefresh(); delete this._maybeDirtyWhileMuted; } delete this._waitForScrollTimeout; this._updateViewportStickinessForTest(); } } _updateViewportStickinessForTest() { // This method is sniffed in tests. } _updateStickToBottomOnWheel() { this._updateStickToBottomOnPointerDown(); this._updateStickToBottomOnPointerUp(); } _promptTextChanged() { const oldStickToBottom = this._viewport.stickToBottom(); const willStickToBottom = this._isScrolledToBottom(); this._viewport.setStickToBottom(willStickToBottom); if (willStickToBottom && !oldStickToBottom) { this._scheduleViewportRefresh(); } this._promptTextChangedForTest(); } _promptTextChangedForTest() { // This method is sniffed in tests. } /** * @return {boolean} */ _isScrolledToBottom() { const distanceToPromptEditorBottom = this._messagesElement.scrollHeight - this._messagesElement.scrollTop - this._messagesElement.clientHeight - /** @type {!HTMLElement} */ (this._prompt.belowEditorElement()).offsetHeight; return distanceToPromptEditorBottom <= 2; } } const persistedHistorySize = 300; /** * @unrestricted */ export class ConsoleViewFilter { /** * @param {function():void} filterChangedCallback */ constructor(filterChangedCallback) { this._filterChanged = filterChangedCallback; this._messageLevelFiltersSetting = ConsoleViewFilter.levelFilterSetting(); this._hideNetworkMessagesSetting = Common.Settings.Settings.instance().moduleSetting('hideNetworkMessages'); this._filterByExecutionContextSetting = Common.Settings.Settings.instance().moduleSetting('selectedContextFilterEnabled'); this._messageLevelFiltersSetting.addChangeListener(this._onFilterChanged.bind(this)); this._hideNetworkMessagesSetting.addChangeListener(this._onFilterChanged.bind(this)); this._filterByExecutionContextSetting.addChangeListener(this._onFilterChanged.bind(this)); UI.Context.Context.instance().addFlavorChangeListener( SDK.RuntimeModel.ExecutionContext, this._onFilterChanged, this); const filterKeys = Object.values(FilterType); this._suggestionBuilder = new UI.FilterSuggestionBuilder.FilterSuggestionBuilder(filterKeys); this._textFilterUI = new UI.Toolbar.ToolbarInput( Common.UIString.UIString('Filter'), '', 0.2, 1, Common.UIString.UIString('e.g. /event\\d/ -cdn url:a.com'), this._suggestionBuilder.completions.bind(this._suggestionBuilder)); this._textFilterSetting = Common.Settings.Settings.instance().createSetting('console.textFilter', ''); if (this._textFilterSetting.get()) { this._textFilterUI.setValue(this._textFilterSetting.get()); } this._textFilterUI.addEventListener(UI.Toolbar.ToolbarInput.Event.TextChanged, () => { this._textFilterSetting.set(this._textFilterUI.value()); this._onFilterChanged(); }); this._filterParser = new TextUtils.TextUtils.FilterParser(filterKeys); this._currentFilter = new ConsoleFilter('', [], null, this._messageLevelFiltersSetting.get()); this._updateCurrentFilter(); /** @type {!Map} */ this._levelLabels = new Map([ [SDK.ConsoleModel.MessageLevel.Verbose, Common.UIString.UIString('Verbose')], [SDK.ConsoleModel.MessageLevel.Info, Common.UIString.UIString('Info')], [SDK.ConsoleModel.MessageLevel.Warning, Common.UIString.UIString('Warnings')], [SDK.ConsoleModel.MessageLevel.Error, Common.UIString.UIString('Errors')], ]); this._levelMenuButton = new UI.Toolbar.ToolbarButton(ls`Log levels`); this._levelMenuButton.turnIntoSelect(); this._levelMenuButton.addEventListener( UI.Toolbar.ToolbarButton.Events.Click, this._showLevelContextMenu.bind(this)); UI.ARIAUtils.markAsMenuButton(this._levelMenuButton.element); this._updateLevelMenuButtonText(); this._messageLevelFiltersSetting.addChangeListener(this._updateLevelMenuButtonText.bind(this)); } /** * @param {!SDK.ConsoleModel.ConsoleMessage} message */ onMessageAdded(message) { if (message.type === SDK.ConsoleModel.MessageType.Command || message.type === SDK.ConsoleModel.MessageType.Result || message.isGroupMessage()) { return; } if (message.context) { this._suggestionBuilder.addItem(FilterType.Context, message.context); } if (message.source) { this._suggestionBuilder.addItem(FilterType.Source, message.source); } if (message.url) { this._suggestionBuilder.addItem(FilterType.Url, message.url); } } /** * @return {!Common.Settings.Setting>} */ static levelFilterSetting() { return Common.Settings.Settings.instance().createSetting( 'messageLevelFilters', ConsoleFilter.defaultLevelsFilterValue()); } _updateCurrentFilter() { const parsedFilters = this._filterParser.parse(this._textFilterUI.value()); if (this._hideNetworkMessagesSetting.get()) { parsedFilters.push( {key: FilterType.Source, text: SDK.ConsoleModel.MessageSource.Network, negative: true, regex: undefined}); } this._currentFilter.executionContext = this._filterByExecutionContextSetting.get() ? UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext) : null; this._currentFilter.parsedFilters = parsedFilters; this._currentFilter.levelsMask = this._messageLevelFiltersSetting.get(); } _onFilterChanged() { this._updateCurrentFilter(); this._filterChanged(); } _updateLevelMenuButtonText() { let isAll = true; let isDefault = true; const allValue = ConsoleFilter.allLevelsFilterValue(); const defaultValue = ConsoleFilter.defaultLevelsFilterValue(); let text = null; const levels = this._messageLevelFiltersSetting.get(); for (const name of Object.values(SDK.ConsoleModel.MessageLevel)) { isAll = isAll && levels[name] === allValue[name]; isDefault = isDefault && levels[name] === defaultValue[name]; if (levels[name]) { text = text ? Common.UIString.UIString('Custom levels') : Common.UIString.UIString('%s only', this._levelLabels.get(name)); } } if (isAll) { text = Common.UIString.UIString('All levels'); } else if (isDefault) { text = Common.UIString.UIString('Default levels'); } else { text = text || Common.UIString.UIString('Hide all'); } this._levelMenuButton.element.classList.toggle('warning', !isAll && !isDefault); this._levelMenuButton.setText(text); this._levelMenuButton.setTitle(ls`Log level: ${text}`); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _showLevelContextMenu(event) { const mouseEvent = /** @type {!Event} */ (event.data); const setting = this._messageLevelFiltersSetting; const levels = setting.get(); const contextMenu = new UI.ContextMenu.ContextMenu( mouseEvent, true /* useSoftMenu */, this._levelMenuButton.element.totalOffsetLeft(), this._levelMenuButton.element.totalOffsetTop() + /** @type {!HTMLElement} */ (this._levelMenuButton.element).offsetHeight); contextMenu.headerSection().appendItem( Common.UIString.UIString('Default'), () => setting.set(ConsoleFilter.defaultLevelsFilterValue())); for (const [level, levelText] of this._levelLabels.entries()) { contextMenu.defaultSection().appendCheckboxItem(levelText, toggleShowLevel.bind(null, level), levels[level]); } contextMenu.show(); /** * @param {string} level */ function toggleShowLevel(level) { levels[level] = !levels[level]; setting.set(levels); } } /** * @param {string} url */ addMessageURLFilter(url) { if (!url) { return; } const suffix = this._textFilterUI.value() ? ` ${this._textFilterUI.value()}` : ''; this._textFilterUI.setValue(`-url:${url}${suffix}`); this._textFilterSetting.set(this._textFilterUI.value()); this._onFilterChanged(); } /** * @param {!ConsoleViewMessage} viewMessage * @return {boolean} */ shouldBeVisible(viewMessage) { return this._currentFilter.shouldBeVisible(viewMessage); } clear() { this._suggestionBuilder.clear(); } reset() { this._messageLevelFiltersSetting.set(ConsoleFilter.defaultLevelsFilterValue()); this._filterByExecutionContextSetting.set(false); this._hideNetworkMessagesSetting.set(false); this._textFilterUI.setValue(''); this._onFilterChanged(); } } /** * @unrestricted */ export class ConsoleGroup { /** * @param {?ConsoleGroup} parentGroup * @param {?ConsoleGroupViewMessage} groupMessage */ constructor(parentGroup, groupMessage) { this._parentGroup = parentGroup; this._nestingLevel = parentGroup ? parentGroup.nestingLevel() + 1 : 0; this._messagesHidden = groupMessage && groupMessage.collapsed() || this._parentGroup && this._parentGroup.messagesHidden() || false; } /** * @return {!ConsoleGroup} */ static createTopGroup() { return new ConsoleGroup(null, null); } /** * @return {boolean} */ messagesHidden() { return this._messagesHidden; } /** * @return {number} */ nestingLevel() { return this._nestingLevel; } /** * @return {?ConsoleGroup} */ parentGroup() { return this._parentGroup; } } /** * @implements {UI.ActionDelegate.ActionDelegate} */ export class ActionDelegate { /** * @override * @param {!UI.Context.Context} context * @param {string} actionId * @return {boolean} */ handleAction(context, actionId) { switch (actionId) { case 'console.show': Host.InspectorFrontendHost.InspectorFrontendHostInstance.bringToFront(); Common.Console.Console.instance().show(); ConsoleView.instance()._focusPrompt(); return true; case 'console.clear': ConsoleView.clearConsole(); return true; case 'console.clear.history': ConsoleView.instance()._clearHistory(); return true; case 'console.create-pin': ConsoleView.instance()._pinPane.addPin('', true /* userGesture */); return true; } return false; } } /** @type {!WeakMap} */ const messagesSortedBySymbol = new WeakMap(); /** @type {!WeakMap} */ const consoleMessageToViewMessage = new WeakMap(); /** * @typedef {{messageIndex: number, matchIndex: number}} */ // @ts-expect-error typedef export let RegexMatchRange;