| /* |
| * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. |
| * |
| * 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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. |
| */ |
| |
| // FIXME: <https://webkit.org/b/143545> Web Inspector: LogContentView should use higher level objects |
| |
| WI.LogContentView = class LogContentView extends WI.ContentView |
| { |
| constructor(representedObject) |
| { |
| super(representedObject); |
| |
| this._nestingLevel = 0; |
| this._selectedMessages = []; |
| this._immediatelyHiddenMessages = new Set; |
| |
| // FIXME: Try to use a marker, instead of a list of messages that get re-added. |
| this._provisionalMessages = []; |
| |
| this.element.classList.add("log"); |
| |
| this.messagesElement = document.createElement("div"); |
| this.messagesElement.classList.add("console-messages"); |
| this.messagesElement.tabIndex = 0; |
| this.messagesElement.setAttribute("role", "log"); |
| this.messagesElement.addEventListener("mousedown", this._mousedown.bind(this)); |
| this.messagesElement.addEventListener("keydown", this._keyDown.bind(this)); |
| this.messagesElement.addEventListener("keypress", this._keyPress.bind(this)); |
| this.messagesElement.addEventListener("dragstart", this._ondragstart.bind(this), true); |
| this.messagesElement.addEventListener("dragover", this._handleDragOver.bind(this)); |
| this.messagesElement.addEventListener("drop", this._handleDrop.bind(this)); |
| this.element.appendChild(this.messagesElement); |
| |
| this.prompt = WI.quickConsole.prompt; |
| |
| this._keyboardShortcutCommandA = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "A"); |
| this._keyboardShortcutEsc = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape); |
| |
| this._logViewController = new WI.JavaScriptLogViewController(this.messagesElement, this.messagesElement, this.prompt, this, "console-prompt-history"); |
| this._lastMessageView = null; |
| |
| const fixed = true; |
| this._findBanner = new WI.FindBanner(this, "console-find-banner", fixed); |
| this._findBanner.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._findBanner.targetElement = this.element; |
| |
| this._currentSearchQuery = ""; |
| this._searchMatches = []; |
| this._selectedSearchMatch = null; |
| this._selectedSearchMatchIsValid = false; |
| |
| this._preserveLogNavigationItem = new WI.CheckboxNavigationItem("preserve-log", WI.UIString("Preserve Log"), !WI.settings.clearLogOnNavigate.value); |
| this._preserveLogNavigationItem.tooltip = WI.UIString("Do not clear the console on new page loads"); |
| this._preserveLogNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, () => { |
| WI.settings.clearLogOnNavigate.value = !WI.settings.clearLogOnNavigate.value; |
| }); |
| WI.settings.clearLogOnNavigate.addEventListener(WI.Setting.Event.Changed, this._handleClearLogOnNavigateSettingChanged, this); |
| |
| this._emulateInUserGestureNavigationItem = new WI.CheckboxNavigationItem("emulate-in-user-gesture", WI.UIString("Emulate User Gesture"), WI.settings.emulateInUserGesture.value); |
| this._emulateInUserGestureNavigationItem.tooltip = WI.UIString("Run console commands as if inside a user gesture"); |
| this._emulateInUserGestureNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, () => { |
| WI.settings.emulateInUserGesture.value = !WI.settings.emulateInUserGesture.value; |
| }); |
| WI.settings.emulateInUserGesture.addEventListener(WI.Setting.Event.Changed, this._handleEmulateInUserGestureSettingChanged, this); |
| |
| this._checkboxesNavigationItemGroup = new WI.GroupNavigationItem([this._preserveLogNavigationItem, this._emulateInUserGestureNavigationItem, new WI.DividerNavigationItem]); |
| |
| let scopeBarItems = [ |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.All, WI.UIString("All"), {exclusive: true}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Evaluations, WI.UIString("Evaluations"), {className: "evaluations"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Errors, WI.UIString("Errors"), {className: "errors"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Warnings, WI.UIString("Warnings"), {className: "warnings"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Logs, WI.UIString("Logs"), {className: "logs"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Infos, WI.UIString("Infos"), {className: "infos", hidden: true}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Debugs, WI.UIString("Debugs"), {className: "debugs", hidden: true}), |
| ]; |
| this._scopeBar = new WI.ScopeBar("log-scope-bar", scopeBarItems, scopeBarItems[0]); |
| this._scopeBar.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; |
| this._scopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionDidChange, this); |
| |
| this._hasNonDefaultLogChannelMessage = false; |
| if (WI.ConsoleManager.supportsLogChannels()) { |
| let messageChannelBarItems = [ |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.AllChannels, WI.UIString("All"), {exclusive: true}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.Media, WI.UIString("Media"), {className: "media"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.MediaSource, WI.UIString("MediaSource"), {className: "mediasource"}), |
| new WI.ScopeBarItem(WI.LogContentView.Scopes.WebRTC, WI.UIString("WebRTC"), {className: "webrtc"}), |
| ]; |
| |
| this._messageSourceBar = new WI.ScopeBar("message-channel-scope-bar", messageChannelBarItems, messageChannelBarItems[0]); |
| this._messageSourceBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._messageSourceBarSelectionDidChange, this); |
| } |
| |
| this._garbageCollectNavigationItem = new WI.ButtonNavigationItem("garbage-collect", WI.UIString("Collect garbage"), "Images/NavigationItemGarbageCollect.svg", 16, 16); |
| this._garbageCollectNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._garbageCollect, this); |
| |
| this._clearLogNavigationItem = new WI.ButtonNavigationItem("clear-log", WI.UIString("Clear log (%s or %s)").format(WI.clearKeyboardShortcut.displayName, this._logViewController.messagesAlternateClearKeyboardShortcut.displayName), "Images/NavigationItemTrash.svg", 15, 15); |
| this._clearLogNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High; |
| this._clearLogNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._clearLog, this); |
| |
| this._showConsoleTabNavigationItem = new WI.ButtonNavigationItem("show-tab", WI.UIString("Show Console tab"), "Images/SplitToggleUp.svg", 16, 16); |
| this._showConsoleTabNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.High; |
| this._showConsoleTabNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._showConsoleTab, this); |
| |
| this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); |
| |
| WI.consoleManager.addEventListener(WI.ConsoleManager.Event.SessionStarted, this._sessionStarted, this); |
| WI.consoleManager.addEventListener(WI.ConsoleManager.Event.MessageAdded, this._messageAdded, this); |
| WI.consoleManager.addEventListener(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, this._previousMessageRepeatCountUpdated, this); |
| WI.consoleManager.addEventListener(WI.ConsoleManager.Event.Cleared, this._logCleared, this); |
| |
| WI.Frame.addEventListener(WI.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this); |
| } |
| |
| // Public |
| |
| get navigationItems() |
| { |
| let navigationItems = [ |
| this._findBanner, |
| this._checkboxesNavigationItemGroup, |
| new WI.DividerNavigationItem, |
| this._scopeBar, |
| new WI.DividerNavigationItem |
| ]; |
| |
| if (this._hasNonDefaultLogChannelMessage && this._messageSourceBar) |
| navigationItems.push(this._messageSourceBar, new WI.DividerNavigationItem); |
| |
| if (InspectorBackend.hasCommand("Heap.gc")) |
| navigationItems.push(this._garbageCollectNavigationItem); |
| |
| navigationItems.push(this._clearLogNavigationItem); |
| |
| if (WI.isShowingSplitConsole()) |
| navigationItems.push(new WI.DividerNavigationItem, this._showConsoleTabNavigationItem); |
| |
| return navigationItems; |
| } |
| |
| get scopeBar() |
| { |
| return this._scopeBar; |
| } |
| |
| get logViewController() |
| { |
| return this._logViewController; |
| } |
| |
| get scrollableElements() |
| { |
| return [this.messagesElement]; |
| } |
| |
| get shouldKeepElementsScrolledToBottom() |
| { |
| return true; |
| } |
| |
| shown() |
| { |
| super.shown(); |
| |
| this._logViewController.renderPendingMessages(); |
| } |
| |
| closed() |
| { |
| // While it may be possible to get here, this is a singleton ContentView instance |
| // that is often re-inserted back into different ContentBrowsers, so we shouldn't |
| // remove the event listeners. The singleton will never go away anyways. |
| console.assert(this === WI.consoleContentView); |
| |
| super.closed(); |
| } |
| |
| didAppendConsoleMessageView(messageView) |
| { |
| console.assert(messageView instanceof WI.ConsoleMessageView || messageView instanceof WI.ConsoleCommandView); |
| |
| // Nest the message. |
| var type = messageView instanceof WI.ConsoleCommandView ? null : messageView.message.type; |
| if (this._nestingLevel && type !== WI.ConsoleMessage.MessageType.EndGroup) { |
| var x = 16 * this._nestingLevel; |
| var messageElement = messageView.element; |
| messageElement.style.left = x + "px"; |
| messageElement.style.width = "calc(100% - " + x + "px)"; |
| } |
| |
| // Update the nesting level. |
| switch (type) { |
| case WI.ConsoleMessage.MessageType.StartGroup: |
| case WI.ConsoleMessage.MessageType.StartGroupCollapsed: |
| ++this._nestingLevel; |
| break; |
| case WI.ConsoleMessage.MessageType.EndGroup: |
| if (this._nestingLevel > 0) |
| --this._nestingLevel; |
| break; |
| } |
| |
| this._clearFocusableChildren(); |
| |
| // Some results don't populate until further backend dispatches occur (like the DOM tree). |
| // We want to remove focusable children after those pending dispatches too. |
| let target = messageView.message ? messageView.message.target : WI.runtimeManager.activeExecutionContext.target; |
| target.connection.runAfterPendingDispatches(this._clearFocusableChildren.bind(this)); |
| |
| if (!this._scopeBar.item(WI.LogContentView.Scopes.All).selected) { |
| if (messageView instanceof WI.ConsoleCommandView || messageView.message instanceof WI.ConsoleCommandResultMessage) |
| this._scopeBar.item(WI.LogContentView.Scopes.Evaluations).toggle(true, {extendSelection: true}); |
| } |
| |
| console.assert(messageView.element instanceof Element); |
| this._filterMessageElements([messageView.element]); |
| |
| if (!this._isMessageVisible(messageView.element)) { |
| this._immediatelyHiddenMessages.add(messageView); |
| this._showHiddenMessagesBannerIfNeeded(); |
| } |
| |
| this._lastMessageView = messageView; |
| } |
| |
| get supportsSearch() { return true; } |
| get numberOfSearchResults() { return this.hasPerformedSearch ? this._searchMatches.length : null; } |
| get hasPerformedSearch() { return this._currentSearchQuery !== ""; } |
| |
| get supportsCustomFindBanner() |
| { |
| return true; |
| } |
| |
| showCustomFindBanner() |
| { |
| if (!this.visible) |
| return; |
| |
| this._findBanner.focus(); |
| } |
| |
| get supportsSave() |
| { |
| if (!this.visible) |
| return false; |
| |
| if (WI.isShowingSplitConsole()) |
| return false; |
| |
| return true; |
| } |
| |
| get saveData() |
| { |
| return { |
| content: this._formatMessagesAsData(false), |
| suggestedName: WI.UIString("Console") + ".txt", |
| forceSaveAs: true, |
| }; |
| } |
| |
| handleCopyEvent(event) |
| { |
| if (!this._selectedMessages.length) |
| return; |
| |
| event.clipboardData.setData("text/plain", this._formatMessagesAsData(true)); |
| event.stopPropagation(); |
| event.preventDefault(); |
| } |
| |
| handleClearShortcut(event) |
| { |
| this._logViewController.requestClearMessages(); |
| } |
| |
| handlePopulateFindShortcut() |
| { |
| let searchQuery = this.searchQueryWithSelection(); |
| if (!searchQuery) |
| return; |
| |
| this._findBanner.searchQuery = searchQuery; |
| |
| this.performSearch(this._findBanner.searchQuery); |
| } |
| |
| handleFindNextShortcut() |
| { |
| this.findBannerRevealNextResult(this._findBanner); |
| } |
| |
| handleFindPreviousShortcut() |
| { |
| this.findBannerRevealPreviousResult(this._findBanner); |
| } |
| |
| findBannerRevealPreviousResult() |
| { |
| this.highlightPreviousSearchMatch(); |
| } |
| |
| highlightPreviousSearchMatch() |
| { |
| if (!this.hasPerformedSearch || isEmptyObject(this._searchMatches)) |
| return; |
| |
| var index = this._selectedSearchMatch ? this._searchMatches.indexOf(this._selectedSearchMatch) : this._searchMatches.length; |
| this._highlightSearchMatchAtIndex(index - 1); |
| } |
| |
| findBannerRevealNextResult() |
| { |
| this.highlightNextSearchMatch(); |
| } |
| |
| highlightNextSearchMatch() |
| { |
| if (!this.hasPerformedSearch || isEmptyObject(this._searchMatches)) |
| return; |
| |
| var index = this._selectedSearchMatch ? this._searchMatches.indexOf(this._selectedSearchMatch) + 1 : 0; |
| this._highlightSearchMatchAtIndex(index); |
| } |
| |
| findBannerWantsToClearAndBlur(findBanner) |
| { |
| if (this._selectedMessages.length) |
| this.messagesElement.focus(); |
| else |
| this.prompt.focus(); |
| } |
| |
| // Private |
| |
| _formatMessagesAsData(onlySelected) |
| { |
| var messages = this._allMessageElements(); |
| |
| if (onlySelected) { |
| messages = messages.filter(function(message) { |
| return message.classList.contains(WI.LogContentView.SelectedStyleClassName); |
| }); |
| } |
| |
| var data = ""; |
| |
| var isPrefixOptional = messages.length <= 1 && onlySelected; |
| messages.forEach(function(messageElement, index) { |
| var messageView = messageElement.__messageView || messageElement.__commandView; |
| if (!messageView) |
| return; |
| |
| if (index > 0) |
| data += "\n"; |
| data += messageView.toClipboardString(isPrefixOptional); |
| }); |
| |
| return data; |
| } |
| |
| _sessionStarted(event) |
| { |
| if (WI.settings.clearLogOnNavigate.value) { |
| this._reappendProvisionalMessages(); |
| return; |
| } |
| |
| for (let messageElement of this._allMessageElements()) { |
| if (messageElement.__messageView) |
| messageElement.__messageView.clearSessionState(); |
| } |
| |
| const isFirstSession = false; |
| const newSessionReason = event.data.wasReloaded ? WI.ConsoleSession.NewSessionReason.PageReloaded : WI.ConsoleSession.NewSessionReason.PageNavigated; |
| this._logViewController.startNewSession(isFirstSession, {newSessionReason, timestamp: event.data.timestamp}); |
| |
| this._clearProvisionalState(); |
| } |
| |
| _scopeFromMessageSource(source) |
| { |
| switch (source) { |
| case WI.ConsoleMessage.MessageSource.Media: |
| return WI.LogContentView.Scopes.Media; |
| case WI.ConsoleMessage.MessageSource.WebRTC: |
| return WI.LogContentView.Scopes.WebRTC; |
| case WI.ConsoleMessage.MessageSource.MediaSource: |
| return WI.LogContentView.Scopes.MediaSource; |
| } |
| |
| return undefined; |
| } |
| |
| _scopeFromMessageLevel(level) |
| { |
| switch (level) { |
| case WI.ConsoleMessage.MessageLevel.Warning: |
| return WI.LogContentView.Scopes.Warnings; |
| case WI.ConsoleMessage.MessageLevel.Error: |
| return WI.LogContentView.Scopes.Errors; |
| case WI.ConsoleMessage.MessageLevel.Log: |
| return WI.LogContentView.Scopes.Logs; |
| case WI.ConsoleMessage.MessageLevel.Info: |
| return this._hasNonDefaultLogChannelMessage ? WI.LogContentView.Scopes.Infos : WI.LogContentView.Scopes.Logs; |
| case WI.ConsoleMessage.MessageLevel.Debug: |
| return this._hasNonDefaultLogChannelMessage ? WI.LogContentView.Scopes.Debugs : WI.LogContentView.Scopes.Logs; |
| } |
| console.assert(false, "This should not be reached."); |
| |
| return undefined; |
| } |
| |
| _messageAdded(event) |
| { |
| let message = event.data.message; |
| if (this._startedProvisionalLoad) |
| this._provisionalMessages.push(message); |
| |
| if (!this._hasNonDefaultLogChannelMessage && WI.consoleManager.logChannelSources.includes(message.source)) { |
| this._hasNonDefaultLogChannelMessage = true; |
| this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange); |
| this._scopeBar.item(WI.LogContentView.Scopes.Infos).hidden = false; |
| this._scopeBar.item(WI.LogContentView.Scopes.Debugs).hidden = false; |
| } |
| |
| this._logViewController.appendConsoleMessage(message); |
| } |
| |
| _previousMessageRepeatCountUpdated(event) |
| { |
| if (!this._logViewController.updatePreviousMessageRepeatCount(event.data.count)) |
| return; |
| |
| if (this._lastMessageView) { |
| if (this._isMessageVisible(this._lastMessageView.element)) |
| return; |
| |
| this._immediatelyHiddenMessages.add(this._lastMessageView); |
| } |
| |
| this._showHiddenMessagesBannerIfNeeded(); |
| } |
| |
| _handleContextMenuEvent(event) |
| { |
| if (!window.getSelection().isCollapsed) { |
| // If there is a selection, we want to show our normal context menu |
| // (with Copy, etc.), and not Clear Log. |
| return; |
| } |
| |
| // In the case that there are selected messages, only clear that selection if the right-click |
| // is not on the element or descendants of the selected messages. |
| if (this._selectedMessages.length && !this._selectedMessages.some(element => element.contains(event.target))) { |
| this._clearMessagesSelection(); |
| this._mousedown(event); |
| } |
| |
| // If there are no selected messages, right-clicking will not reset the current mouse state |
| // meaning that when the context menu is dismissed, console messages will be selected when |
| // the user moves the mouse even though no buttons are pressed. |
| if (!this._selectedMessages.length) |
| this._mouseup(event); |
| |
| // We don't want to show the custom menu for links in the console. |
| if (event.target.closest("a")) |
| return; |
| |
| let contextMenu = WI.ContextMenu.createFromEvent(event); |
| |
| contextMenu.appendSeparator(); |
| |
| if (this._selectedMessages.length) { |
| contextMenu.appendItem(WI.UIString("Copy Selected"), () => { |
| InspectorFrontendHost.copyText(this._formatMessagesAsData(true)); |
| }); |
| |
| contextMenu.appendItem(WI.UIString("Save Selected"), () => { |
| const forceSaveAs = true; |
| WI.FileUtilities.save({ |
| content: this._formatMessagesAsData(true), |
| suggestedName: WI.UIString("Console") + ".txt", |
| }, forceSaveAs); |
| }); |
| |
| contextMenu.appendSeparator(); |
| } |
| |
| contextMenu.appendItem(WI.UIString("Clear Log"), this._clearLog.bind(this)); |
| contextMenu.appendSeparator(); |
| } |
| |
| _mousedown(event) |
| { |
| if (this._selectedMessages.length && (event.button !== 0 || event.ctrlKey)) |
| return; |
| |
| if (event.defaultPrevented) { |
| // Default was prevented on the event, so this means something deeper (like a disclosure triangle) |
| // handled the mouse down. In this case we want to clear the selection and don't make a new selection. |
| this._clearMessagesSelection(); |
| return; |
| } |
| |
| this._mouseDownWrapper = event.target.closest("." + WI.LogContentView.ItemWrapperStyleClassName); |
| this._mouseDownShiftKey = event.shiftKey; |
| this._mouseDownCommandKey = event.metaKey; |
| this._mouseMoveIsRowSelection = false; |
| |
| window.addEventListener("mousemove", this); |
| window.addEventListener("mouseup", this); |
| } |
| |
| _targetInMessageCanBeSelected(target, message) |
| { |
| if (target.closest("a")) |
| return false; |
| return true; |
| } |
| |
| _mousemove(event) |
| { |
| var selection = window.getSelection(); |
| var wrapper = event.target.closest("." + WI.LogContentView.ItemWrapperStyleClassName); |
| |
| if (!wrapper) { |
| // No wrapper under the mouse, so look at the selection to try and find one. |
| if (!selection.isCollapsed) { |
| let focusElement = selection.focusNode; |
| if (!(focusElement instanceof Element)) |
| focusElement = focusElement.parentElement; |
| wrapper = focusElement.closest("." + WI.LogContentView.ItemWrapperStyleClassName); |
| } |
| |
| if (!wrapper) { |
| selection.removeAllRanges(); |
| return; |
| } |
| } |
| |
| if (!selection.isCollapsed) |
| this._clearMessagesSelection(); |
| |
| if (wrapper === this._mouseDownWrapper && !this._mouseMoveIsRowSelection) |
| return; |
| |
| // Don't change the selection if the mouse has moved outside of the view (e.g. for faster scrolling). |
| if (!this.element.contains(event.target)) |
| return; |
| |
| selection.removeAllRanges(); |
| |
| if (!this._mouseMoveIsRowSelection) |
| this._updateMessagesSelection(this._mouseDownWrapper, this._mouseDownCommandKey, this._mouseDownShiftKey, false); |
| |
| this._updateMessagesSelection(wrapper, false, true, false); |
| |
| this._mouseMoveIsRowSelection = true; |
| |
| event.preventDefault(); |
| event.stopPropagation(); |
| } |
| |
| _mouseup(event) |
| { |
| window.removeEventListener("mousemove", this); |
| window.removeEventListener("mouseup", this); |
| |
| var selection = window.getSelection(); |
| var wrapper = event.target.closest("." + WI.LogContentView.ItemWrapperStyleClassName); |
| |
| if (wrapper && (selection.isCollapsed || event.shiftKey)) { |
| selection.removeAllRanges(); |
| |
| if (this._targetInMessageCanBeSelected(event.target, wrapper)) { |
| var sameWrapper = wrapper === this._mouseDownWrapper; |
| this._updateMessagesSelection(wrapper, sameWrapper ? this._mouseDownCommandKey : false, sameWrapper ? this._mouseDownShiftKey : true, false); |
| } |
| } else if (!selection.isCollapsed) { |
| // There is a text selection, clear the row selection. |
| this._clearMessagesSelection(); |
| } else if (!this._mouseDownWrapper) { |
| // The mouse didn't hit a console item, so clear the row selection. |
| this._clearMessagesSelection(); |
| |
| // Focus the prompt. Focusing the prompt needs to happen after the click to work. |
| setTimeout(() => { this.prompt.focus(); }, 0); |
| } |
| |
| delete this._mouseMoveIsRowSelection; |
| delete this._mouseDownWrapper; |
| delete this._mouseDownShiftKey; |
| delete this._mouseDownCommandKey; |
| } |
| |
| _ondragstart(event) |
| { |
| if (event.target.closest("." + WI.DOMTreeOutline.StyleClassName)) { |
| event.stopPropagation(); |
| event.preventDefault(); |
| } |
| } |
| |
| _handleDragOver(event) |
| { |
| if (event.dataTransfer.types.includes(WI.DOMTreeOutline.DOMNodeIdDragType)) { |
| event.preventDefault(); |
| event.dataTransfer.dropEffect = "copy"; |
| } |
| } |
| |
| _handleDrop(event) |
| { |
| let domNodeId = event.dataTransfer.getData(WI.DOMTreeOutline.DOMNodeIdDragType); |
| if (domNodeId) { |
| event.preventDefault(); |
| |
| let domNode = WI.domManager.nodeForId(domNodeId); |
| WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup) |
| .then((remoteObject) => { |
| let text = domNode.nodeType() === Node.ELEMENT_NODE ? WI.UIString("Dropped Element") : WI.UIString("Dropped Node"); |
| const addSpecialUserLogClass = true; |
| WI.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, addSpecialUserLogClass); |
| |
| this.prompt.focus(); |
| }); |
| } |
| } |
| |
| handleEvent(event) |
| { |
| switch (event.type) { |
| case "mousemove": |
| this._mousemove(event); |
| break; |
| case "mouseup": |
| this._mouseup(event); |
| break; |
| } |
| } |
| |
| _updateMessagesSelection(message, multipleSelection, rangeSelection, shouldScrollIntoView) |
| { |
| console.assert(message); |
| if (!message) |
| return; |
| |
| var alreadySelectedMessage = this._selectedMessages.includes(message); |
| if (alreadySelectedMessage && this._selectedMessages.length && multipleSelection) { |
| message.classList.remove(WI.LogContentView.SelectedStyleClassName); |
| this._selectedMessages.remove(message); |
| return; |
| } |
| |
| if (!multipleSelection && !rangeSelection) |
| this._clearMessagesSelection(); |
| |
| if (rangeSelection) { |
| var messages = this._visibleMessageElements(); |
| |
| var refIndex = this._referenceMessageForRangeSelection ? messages.indexOf(this._referenceMessageForRangeSelection) : 0; |
| var targetIndex = messages.indexOf(message); |
| |
| var newRange = [Math.min(refIndex, targetIndex), Math.max(refIndex, targetIndex)]; |
| |
| if (this._selectionRange && this._selectionRange[0] === newRange[0] && this._selectionRange[1] === newRange[1]) |
| return; |
| |
| var startIndex = this._selectionRange ? Math.min(this._selectionRange[0], newRange[0]) : newRange[0]; |
| var endIndex = this._selectionRange ? Math.max(this._selectionRange[1], newRange[1]) : newRange[1]; |
| |
| for (var i = startIndex; i <= endIndex; ++i) { |
| var messageInRange = messages[i]; |
| if (i >= newRange[0] && i <= newRange[1] && !messageInRange.classList.contains(WI.LogContentView.SelectedStyleClassName)) { |
| messageInRange.classList.add(WI.LogContentView.SelectedStyleClassName); |
| this._selectedMessages.push(messageInRange); |
| } else if (i < newRange[0] || i > newRange[1] && messageInRange.classList.contains(WI.LogContentView.SelectedStyleClassName)) { |
| messageInRange.classList.remove(WI.LogContentView.SelectedStyleClassName); |
| this._selectedMessages.remove(messageInRange); |
| } |
| } |
| |
| this._selectionRange = newRange; |
| } else { |
| message.classList.add(WI.LogContentView.SelectedStyleClassName); |
| this._selectedMessages.push(message); |
| this._selectionRange = null; |
| } |
| |
| if (!rangeSelection) |
| this._referenceMessageForRangeSelection = message; |
| |
| if (shouldScrollIntoView && !alreadySelectedMessage) { |
| let lastMessage = this._selectedMessages.lastValue; |
| if (lastMessage) |
| lastMessage.scrollIntoViewIfNeeded(); |
| } |
| } |
| |
| _isMessageVisible(message) |
| { |
| var node = message; |
| |
| if (node.classList.contains(WI.LogContentView.FilteredOutStyleClassName)) |
| return false; |
| |
| if (this.hasPerformedSearch && node.classList.contains(WI.LogContentView.FilteredOutBySearchStyleClassName)) |
| return false; |
| |
| if (message.classList.contains("console-group-title")) |
| node = node.parentNode.parentNode; |
| |
| while (node && node !== this.messagesElement) { |
| if (node.classList.contains("collapsed")) |
| return false; |
| node = node.parentNode; |
| } |
| |
| return true; |
| } |
| |
| _isMessageSelected(message) |
| { |
| return message.classList.contains(WI.LogContentView.SelectedStyleClassName); |
| } |
| |
| _clearMessagesSelection() |
| { |
| this._selectedMessages.forEach(function(message) { |
| message.classList.remove(WI.LogContentView.SelectedStyleClassName); |
| }); |
| this._selectedMessages = []; |
| delete this._referenceMessageForRangeSelection; |
| } |
| |
| _selectAllMessages() |
| { |
| this._clearMessagesSelection(); |
| |
| var messages = this._visibleMessageElements(); |
| for (var i = 0; i < messages.length; ++i) { |
| var message = messages[i]; |
| message.classList.add(WI.LogContentView.SelectedStyleClassName); |
| this._selectedMessages.push(message); |
| } |
| } |
| |
| _allMessageElements() |
| { |
| return Array.from(this.messagesElement.querySelectorAll(".console-message, .console-user-command")); |
| } |
| |
| _unfilteredMessageElements() |
| { |
| return this._allMessageElements().filter(function(message) { |
| return !message.classList.contains(WI.LogContentView.FilteredOutStyleClassName); |
| }); |
| } |
| |
| _visibleMessageElements() |
| { |
| var unfilteredMessages = this._unfilteredMessageElements(); |
| |
| if (!this.hasPerformedSearch) |
| return unfilteredMessages; |
| |
| return unfilteredMessages.filter(function(message) { |
| return !message.classList.contains(WI.LogContentView.FilteredOutBySearchStyleClassName); |
| }); |
| } |
| |
| _logCleared(event) |
| { |
| for (let messageElement of this._allMessageElements()) { |
| if (messageElement.__messageView) |
| messageElement.__messageView.clearSessionState(); |
| } |
| |
| this._logViewController.clear(); |
| |
| this._nestingLevel = 0; |
| this._selectedMessages = []; |
| this._immediatelyHiddenMessages.clear(); |
| |
| if (this._currentSearchQuery) |
| this.performSearch(this._currentSearchQuery); |
| |
| this._showHiddenMessagesBannerIfNeeded(); |
| } |
| |
| _showConsoleTab() |
| { |
| const requestedScope = null; |
| WI.showConsoleTab(requestedScope, { |
| initiatorHint: WI.TabBrowser.TabNavigationInitiator.ButtonClick, |
| }); |
| } |
| |
| _clearLog() |
| { |
| WI.consoleManager.requestClearMessages(); |
| } |
| |
| _garbageCollect() |
| { |
| // COMPATIBILITY (iOS 10.3): Worker targets did not support Heap.gc. |
| for (let target of WI.targets) { |
| if (target.hasDomain("Heap")) |
| target.HeapAgent.gc(); |
| } |
| } |
| |
| _messageShouldBeVisible(message) |
| { |
| let messageSource = this._messageSourceBar && this._scopeFromMessageSource(message.source); |
| if (messageSource && !this._messageSourceBar.item(messageSource).selected && !this._messageSourceBar.item(WI.LogContentView.Scopes.AllChannels).selected) |
| return false; |
| |
| let messageLevel = this._scopeFromMessageLevel(message.level); |
| if (messageLevel) |
| return this._scopeBar.item(messageLevel).selected || this._scopeBar.item(WI.LogContentView.Scopes.All).selected; |
| |
| return true; |
| } |
| |
| _messageSourceBarSelectionDidChange(event) |
| { |
| let items = this._messageSourceBar.selectedItems; |
| if (items.some((item) => item.id === WI.LogContentView.Scopes.AllChannels)) |
| items = this._messageSourceBar.items; |
| |
| this._filterMessageElements(this._allMessageElements()); |
| |
| this._showHiddenMessagesBannerIfNeeded(); |
| } |
| |
| _scopeBarSelectionDidChange(event) |
| { |
| let items = this._scopeBar.selectedItems; |
| if (items.some((item) => item.id === WI.LogContentView.Scopes.All)) |
| items = this._scopeBar.items; |
| |
| this._filterMessageElements(this._allMessageElements()); |
| |
| this._showHiddenMessagesBannerIfNeeded(); |
| } |
| |
| _filterMessageElements(messageElements) |
| { |
| messageElements.forEach(function(messageElement) { |
| let visible = false; |
| if (messageElement.__commandView instanceof WI.ConsoleCommandView || messageElement.__message instanceof WI.ConsoleCommandResultMessage) |
| visible = this._scopeBar.selectedItems.some((item) => item.id === WI.LogContentView.Scopes.Evaluations || item.id === WI.LogContentView.Scopes.All); |
| else |
| visible = this._messageShouldBeVisible(messageElement.__message); |
| |
| let classList = messageElement.classList; |
| if (visible) { |
| classList.remove(WI.LogContentView.FilteredOutStyleClassName); |
| this._immediatelyHiddenMessages.delete(messageElement.__messageView); |
| } else { |
| this._selectedMessages.remove(messageElement); |
| classList.remove(WI.LogContentView.SelectedStyleClassName); |
| classList.add(WI.LogContentView.FilteredOutStyleClassName); |
| } |
| }, this); |
| |
| this.performSearch(this._currentSearchQuery); |
| } |
| |
| _handleClearLogOnNavigateSettingChanged() |
| { |
| this._preserveLogNavigationItem.checked = !WI.settings.clearLogOnNavigate.value; |
| } |
| |
| _handleEmulateInUserGestureSettingChanged() |
| { |
| this._emulateInUserGestureNavigationItem.checked = WI.settings.emulateInUserGesture.value; |
| } |
| |
| _keyDown(event) |
| { |
| if (this._keyboardShortcutCommandA.matchesEvent(event)) |
| this._commandAWasPressed(event); |
| else if (this._keyboardShortcutEsc.matchesEvent(event)) |
| this._escapeWasPressed(event); |
| else if (event.keyIdentifier === "Up") |
| this._upArrowWasPressed(event); |
| else if (event.keyIdentifier === "Down") |
| this._downArrowWasPressed(event); |
| else if (event.keyIdentifier === "Left") |
| this._leftArrowWasPressed(event); |
| else if (event.keyIdentifier === "Right") |
| this._rightArrowWasPressed(event); |
| else if (event.keyIdentifier === "Enter" && event.metaKey) |
| this._commandEnterWasPressed(event); |
| } |
| |
| _keyPress(event) |
| { |
| const isCommandC = event.metaKey && event.keyCode === 99; |
| if (!isCommandC) |
| this.prompt.focus(); |
| } |
| |
| _commandAWasPressed(event) |
| { |
| this._selectAllMessages(); |
| event.preventDefault(); |
| } |
| |
| _escapeWasPressed(event) |
| { |
| if (this._selectedMessages.length) |
| this._clearMessagesSelection(); |
| else |
| this.prompt.focus(); |
| |
| event.preventDefault(); |
| } |
| |
| _upArrowWasPressed(event) |
| { |
| var messages = this._visibleMessageElements(); |
| |
| if (!this._selectedMessages.length) { |
| if (messages.length) |
| this._updateMessagesSelection(messages.lastValue, false, false, true); |
| return; |
| } |
| |
| var lastMessage = this._selectedMessages.lastValue; |
| var previousMessage = this._previousMessage(lastMessage); |
| if (previousMessage) { |
| this._updateMessagesSelection(previousMessage, false, event.shiftKey, true); |
| event.preventDefault(); |
| } else if (!event.shiftKey) { |
| this._clearMessagesSelection(); |
| if (messages.length) { |
| this._updateMessagesSelection(messages[0], false, false, true); |
| event.preventDefault(); |
| } |
| } |
| } |
| |
| _downArrowWasPressed(event) |
| { |
| var messages = this._visibleMessageElements(); |
| |
| if (!this._selectedMessages.length) { |
| if (messages.length) |
| this._updateMessagesSelection(messages[0], false, false, true); |
| return; |
| } |
| |
| var lastMessage = this._selectedMessages.lastValue; |
| var nextMessage = this._nextMessage(lastMessage); |
| if (nextMessage) { |
| this._updateMessagesSelection(nextMessage, false, event.shiftKey, true); |
| event.preventDefault(); |
| } else if (!event.shiftKey) { |
| this._clearMessagesSelection(); |
| if (messages.length) { |
| this._updateMessagesSelection(messages.lastValue, false, false, true); |
| event.preventDefault(); |
| } |
| } |
| } |
| |
| _leftArrowWasPressed(event) |
| { |
| if (this._selectedMessages.length !== 1) |
| return; |
| |
| var currentMessage = this._selectedMessages[0]; |
| if (currentMessage.classList.contains("console-group-title")) { |
| currentMessage.parentNode.classList.add("collapsed"); |
| event.preventDefault(); |
| } else if (currentMessage.__messageView && currentMessage.__messageView.expandable) { |
| currentMessage.__messageView.collapse(); |
| event.preventDefault(); |
| } |
| } |
| |
| _rightArrowWasPressed(event) |
| { |
| if (this._selectedMessages.length !== 1) |
| return; |
| |
| var currentMessage = this._selectedMessages[0]; |
| if (currentMessage.classList.contains("console-group-title")) { |
| currentMessage.parentNode.classList.remove("collapsed"); |
| event.preventDefault(); |
| } else if (currentMessage.__messageView && currentMessage.__messageView.expandable) { |
| currentMessage.__messageView.expand(); |
| event.preventDefault(); |
| } |
| } |
| |
| _commandEnterWasPressed(event) |
| { |
| if (this._selectedMessages.length !== 1) |
| return; |
| |
| let message = this._selectedMessages[0]; |
| if (message.__commandView && message.__commandView.commandText) { |
| this._logViewController.consolePromptTextCommitted(null, message.__commandView.commandText); |
| event.preventDefault(); |
| } |
| } |
| |
| _previousMessage(message) |
| { |
| var messages = this._visibleMessageElements(); |
| for (var i = messages.indexOf(message) - 1; i >= 0; --i) { |
| if (this._isMessageVisible(messages[i])) |
| return messages[i]; |
| } |
| return null; |
| } |
| |
| _nextMessage(message) |
| { |
| var messages = this._visibleMessageElements(); |
| for (var i = messages.indexOf(message) + 1; i < messages.length; ++i) { |
| if (this._isMessageVisible(messages[i])) |
| return messages[i]; |
| } |
| return null; |
| } |
| |
| _clearFocusableChildren() |
| { |
| var focusableElements = this.messagesElement.querySelectorAll("[tabindex]"); |
| for (var i = 0, count = focusableElements.length; i < count; ++i) |
| focusableElements[i].removeAttribute("tabindex"); |
| } |
| |
| findBannerPerformSearch(findBanner, searchQuery) |
| { |
| this.performSearch(searchQuery); |
| } |
| |
| findBannerSearchCleared() |
| { |
| this.searchCleared(); |
| } |
| |
| revealNextSearchResult() |
| { |
| this.findBannerRevealNextResult(); |
| } |
| |
| revealPreviousSearchResult() |
| { |
| this.findBannerRevealPreviousResult(); |
| } |
| |
| performSearch(searchQuery) |
| { |
| if (!isEmptyObject(this._searchHighlightDOMChanges)) |
| WI.revertDOMChanges(this._searchHighlightDOMChanges); |
| |
| this._currentSearchQuery = searchQuery; |
| this._searchHighlightDOMChanges = []; |
| this._searchMatches = []; |
| this._selectedSearchMatchIsValid = false; |
| this._selectedSearchMatch = null; |
| let numberOfResults = 0; |
| |
| if (this._currentSearchQuery === "") { |
| this.element.classList.remove(WI.LogContentView.SearchInProgressStyleClassName); |
| this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange); |
| return; |
| } |
| |
| this.element.classList.add(WI.LogContentView.SearchInProgressStyleClassName); |
| |
| let searchRegex = WI.SearchUtilities.regExpForString(this._currentSearchQuery, WI.SearchUtilities.defaultSettings); |
| this._unfilteredMessageElements().forEach(function(message) { |
| let matchRanges = []; |
| let text = message.textContent; |
| let match = searchRegex.exec(text); |
| while (match) { |
| numberOfResults++; |
| matchRanges.push({offset: match.index, length: match[0].length}); |
| match = searchRegex.exec(text); |
| } |
| |
| if (!isEmptyObject(matchRanges)) |
| this._highlightRanges(message, matchRanges); |
| |
| let classList = message.classList; |
| if (!isEmptyObject(matchRanges)) |
| classList.remove(WI.LogContentView.FilteredOutBySearchStyleClassName); |
| else |
| classList.add(WI.LogContentView.FilteredOutBySearchStyleClassName); |
| }, this); |
| |
| this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange); |
| |
| this._findBanner.numberOfResults = numberOfResults; |
| |
| if (!this._selectedSearchMatchIsValid && this._selectedSearchMatch) { |
| this._selectedSearchMatch.highlight.classList.remove(WI.LogContentView.SelectedStyleClassName); |
| this._selectedSearchMatch = null; |
| } |
| } |
| |
| searchHidden() |
| { |
| this.searchCleared(); |
| } |
| |
| searchCleared() |
| { |
| this.performSearch(""); |
| } |
| |
| _highlightRanges(message, matchRanges) |
| { |
| var highlightedElements = WI.highlightRangesWithStyleClass(message, matchRanges, WI.LogContentView.HighlightedStyleClassName, this._searchHighlightDOMChanges); |
| |
| console.assert(highlightedElements.length === matchRanges.length); |
| |
| matchRanges.forEach(function(range, index) { |
| this._searchMatches.push({message, range, highlight: highlightedElements[index]}); |
| |
| if (this._selectedSearchMatch && !this._selectedSearchMatchIsValid && this._selectedSearchMatch.message === message) { |
| this._selectedSearchMatchIsValid = this._rangesOverlap(this._selectedSearchMatch.range, range); |
| if (this._selectedSearchMatchIsValid) { |
| delete this._selectedSearchMatch; |
| this._highlightSearchMatchAtIndex(this._searchMatches.length - 1); |
| } |
| } |
| }, this); |
| } |
| |
| _rangesOverlap(range1, range2) |
| { |
| return range1.offset <= range2.offset + range2.length && range2.offset <= range1.offset + range1.length; |
| } |
| |
| _highlightSearchMatchAtIndex(index) |
| { |
| if (index >= this._searchMatches.length) |
| index = 0; |
| else if (index < 0) |
| index = this._searchMatches.length - 1; |
| |
| if (this._selectedSearchMatch) |
| this._selectedSearchMatch.highlight.classList.remove(WI.LogContentView.SelectedStyleClassName); |
| |
| this._selectedSearchMatch = this._searchMatches[index]; |
| this._selectedSearchMatch.highlight.classList.add(WI.LogContentView.SelectedStyleClassName); |
| |
| this._selectedSearchMatch.message.scrollIntoViewIfNeeded(false); |
| } |
| |
| _provisionalLoadStarted() |
| { |
| this._startedProvisionalLoad = true; |
| } |
| |
| _reappendProvisionalMessages() |
| { |
| if (!this._startedProvisionalLoad) |
| return; |
| |
| this._startedProvisionalLoad = false; |
| |
| for (let provisionalMessage of this._provisionalMessages) |
| this._logViewController.appendConsoleMessage(provisionalMessage); |
| |
| this._provisionalMessages = []; |
| } |
| |
| _clearProvisionalState() |
| { |
| this._startedProvisionalLoad = false; |
| this._provisionalMessages = []; |
| } |
| |
| _showHiddenMessagesBannerIfNeeded() |
| { |
| if (!this._immediatelyHiddenMessages.size) { |
| if (this._hiddenMessagesBannerElement) |
| this._hiddenMessagesBannerElement.remove(); |
| return; |
| } |
| |
| if (!this._hiddenMessagesBannerElement) { |
| this._hiddenMessagesBannerElement = document.createElement("div"); |
| this._hiddenMessagesBannerElement.className = "hidden-messages-banner"; |
| |
| this._hiddenMessagesBannerElement.appendChild(document.createTextNode(WI.UIString("There are unread messages that have been filtered"))); |
| |
| let clearFiltersButtonElement = this._hiddenMessagesBannerElement.appendChild(document.createElement("button")); |
| clearFiltersButtonElement.textContent = WI.UIString("Clear Filters"); |
| clearFiltersButtonElement.addEventListener("click", (event) => { |
| this._findBanner.clearAndBlur(); |
| this._scopeBar.resetToDefault(); |
| console.assert(!this._immediatelyHiddenMessages.size); |
| |
| this._hiddenMessagesBannerElement.remove(); |
| }); |
| |
| let dismissBannerIconElement = this._hiddenMessagesBannerElement.appendChild(WI.ImageUtilities.useSVGSymbol("Images/Close.svg", "dismiss", WI.UIString("Dismiss"))); |
| dismissBannerIconElement.addEventListener("click", (event) => { |
| this._immediatelyHiddenMessages.clear(); |
| this._hiddenMessagesBannerElement.remove(); |
| }); |
| } |
| |
| if (this.element.firstChild !== this._hiddenMessagesBannerElement) |
| this.element.insertAdjacentElement("afterbegin", this._hiddenMessagesBannerElement); |
| } |
| }; |
| |
| WI.LogContentView.Scopes = { |
| All: "log-all", |
| Debugs: "log-debugs", |
| Errors: "log-errors", |
| Evaluations: "log-evaluations", |
| Infos: "log-infos", |
| Logs: "log-logs", |
| Warnings: "log-warnings", |
| |
| AllChannels: "log-all-channels", |
| Media: "log-media", |
| MediaSource: "log-mediasource", |
| WebRTC: "log-webrtc", |
| }; |
| |
| WI.LogContentView.ItemWrapperStyleClassName = "console-item"; |
| WI.LogContentView.FilteredOutStyleClassName = "filtered-out"; |
| WI.LogContentView.SelectedStyleClassName = "selected"; |
| WI.LogContentView.SearchInProgressStyleClassName = "search-in-progress"; |
| WI.LogContentView.FilteredOutBySearchStyleClassName = "filtered-out-by-search"; |
| WI.LogContentView.HighlightedStyleClassName = "highlighted"; |