/* * Copyright (C) 2013 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. */ WI.JavaScriptLogViewController = class JavaScriptLogViewController extends WI.Object { constructor(element, scrollElement, textPrompt, delegate, historySettingIdentifier) { super(); console.assert(textPrompt instanceof WI.ConsolePrompt); console.assert(historySettingIdentifier); this._element = element; this._scrollElement = scrollElement; this._promptHistorySetting = new WI.Setting(historySettingIdentifier, null); this._prompt = textPrompt; this._prompt.delegate = this; this._prompt.history = this._promptHistorySetting.value; this.delegate = delegate; this._cleared = true; this._previousMessageView = null; this._lastCommitted = ""; this._repeatCountWasInterrupted = false; this._sessions = []; this._currentSessionOrGroup = null; this.messagesAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._element); this._messagesFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._element); this._messagesFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._element); this._promptAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._prompt.element); this._promptFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._prompt.element); this._promptFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._prompt.element); this._pendingMessagesForSessionOrGroup = new Map; this._scheduledRenderIdentifier = 0; this.startNewSession(); } // Public clear() { this._cleared = true; const clearPreviousSessions = true; this.startNewSession(clearPreviousSessions, {newSessionReason: WI.ConsoleSession.NewSessionReason.ConsoleCleared}); } startNewSession(clearPreviousSessions = false, data = {}) { if (clearPreviousSessions) { this._pendingMessagesForSessionOrGroup.clear(); if (this._sessions.length) { for (let session of this._sessions) session.element.remove(); this._sessions = []; this._currentSessionOrGroup = null; } } // First session shows the time when the console was opened. if (!this._sessions.length) data.timestamp = Date.now(); let lastSession = this._sessions.lastValue; // Remove empty session. if (lastSession && !lastSession.hasMessages() && !this._pendingMessagesForSessionOrGroup.has(lastSession)) { this._sessions.pop(); lastSession.element.remove(); } let consoleSession = new WI.ConsoleSession(data); this._previousMessageView = null; this._lastCommitted = ""; this._repeatCountWasInterrupted = false; this._sessions.push(consoleSession); this._currentSessionOrGroup = consoleSession; this._element.appendChild(consoleSession.element); // Make sure the new session is visible. consoleSession.element.scrollIntoView(); } appendImmediateExecutionWithResult(text, result, addSpecialUserLogClass, shouldRevealConsole) { console.assert(result instanceof WI.RemoteObject); var commandMessageView = new WI.ConsoleCommandView(text, addSpecialUserLogClass ? "special-user-log" : null); this._appendConsoleMessageView(commandMessageView, true); function saveResultCallback(savedResultIndex) { let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, false, savedResultIndex, shouldRevealConsole); let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage); this._appendConsoleMessageView(commandResultMessageView, true); } WI.runtimeManager.saveResult(result, saveResultCallback.bind(this)); } appendConsoleMessage(consoleMessage) { var consoleMessageView = new WI.ConsoleMessageView(consoleMessage); this._appendConsoleMessageView(consoleMessageView); return consoleMessageView; } updatePreviousMessageRepeatCount(count) { console.assert(this._previousMessageView); if (!this._previousMessageView) return false; var previousIgnoredCount = this._previousMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] || 0; var previousVisibleCount = this._previousMessageView.repeatCount; if (!this._repeatCountWasInterrupted) { this._previousMessageView.repeatCount = count - previousIgnoredCount; return true; } var consoleMessage = this._previousMessageView.message; var duplicatedConsoleMessageView = new WI.ConsoleMessageView(consoleMessage); duplicatedConsoleMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] = previousIgnoredCount + previousVisibleCount; duplicatedConsoleMessageView.repeatCount = 1; this._appendConsoleMessageView(duplicatedConsoleMessageView); return true; } isScrolledToBottom() { // Lie about being scrolled to the bottom if we have a pending request to scroll to the bottom soon. return this._scrollToBottomTimeout || this._scrollElement.isScrolledToBottom(); } scrollToBottom() { if (this._scrollToBottomTimeout) return; function delayedWork() { this._scrollToBottomTimeout = null; this._scrollElement.scrollTop = this._scrollElement.scrollHeight; } // Don't scroll immediately so we are not causing excessive layouts when there // are many messages being added at once. this._scrollToBottomTimeout = setTimeout(delayedWork.bind(this), 0); } requestClearMessages() { WI.consoleManager.requestClearMessages(); } // Protected consolePromptHistoryDidChange(prompt) { this._promptHistorySetting.value = this._prompt.history; } consolePromptShouldCommitText(prompt, text, cursorIsAtLastPosition, handler) { // Always commit the text if we are not at the last position. if (!cursorIsAtLastPosition) { handler(true); return; } function parseFinished(error, result, message, range) { handler(result !== InspectorBackend.Enum.Runtime.SyntaxErrorType.Recoverable); } WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.parse(text, parseFinished.bind(this)); } consolePromptTextCommitted(prompt, text) { console.assert(text); if (this._lastCommitted !== text) { let commandMessageView = new WI.ConsoleCommandView(text); this._appendConsoleMessageView(commandMessageView, true); this._lastCommitted = text; } function printResult(result, wasThrown, savedResultIndex) { if (!result || this._cleared) return; let shouldRevealConsole = true; let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, wasThrown, savedResultIndex, shouldRevealConsole); let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage); this._appendConsoleMessageView(commandResultMessageView, true); } let options = { objectGroup: WI.RuntimeManager.ConsoleObjectGroup, includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: false, returnByValue: false, generatePreview: true, saveResult: true, emulateUserGesture: WI.settings.emulateInUserGesture.value, sourceURLAppender: appendWebInspectorConsoleEvaluationSourceURL, }; WI.runtimeManager.evaluateInInspectedWindow(text, options, printResult.bind(this)); } // Private _handleFindNextShortcut() { this.delegate.highlightNextSearchMatch(); } _handleFindPreviousShortcut() { this.delegate.highlightPreviousSearchMatch(); } _appendConsoleMessageView(messageView, repeatCountWasInterrupted) { let pendingMessagesForSession = this._pendingMessagesForSessionOrGroup.get(this._currentSessionOrGroup); if (!pendingMessagesForSession) { pendingMessagesForSession = []; this._pendingMessagesForSessionOrGroup.set(this._currentSessionOrGroup, pendingMessagesForSession); } pendingMessagesForSession.push(messageView); this._cleared = false; this._repeatCountWasInterrupted = repeatCountWasInterrupted || false; if (!repeatCountWasInterrupted) this._previousMessageView = messageView; if (messageView.message && messageView.message.source !== WI.ConsoleMessage.MessageSource.JS) this._lastCommitted = ""; if (WI.consoleContentView.visible) this.renderPendingMessagesSoon(); if (!WI.isShowingConsoleTab() && messageView.message && messageView.message.shouldRevealConsole) WI.showSplitConsole(); } renderPendingMessages() { if (this._scheduledRenderIdentifier) { cancelAnimationFrame(this._scheduledRenderIdentifier); this._scheduledRenderIdentifier = 0; } if (!this._pendingMessagesForSessionOrGroup.size) return; let wasScrolledToBottom = this.isScrolledToBottom(); let savedCurrentConsoleGroup = this._currentSessionOrGroup; let lastMessageView = null; const maxMessagesPerFrame = 100; let renderedMessages = 0; for (let [session, messages] of this._pendingMessagesForSessionOrGroup) { this._currentSessionOrGroup = session; let messagesToRender = messages.splice(0, maxMessagesPerFrame - renderedMessages); for (let message of messagesToRender) { message.render(); this._didRenderConsoleMessageView(message); } lastMessageView = messagesToRender.lastValue; if (!messages.length) this._pendingMessagesForSessionOrGroup.delete(session); renderedMessages += messagesToRender.length; if (renderedMessages >= maxMessagesPerFrame) break; } this._currentSessionOrGroup = savedCurrentConsoleGroup; if (wasScrolledToBottom || lastMessageView instanceof WI.ConsoleCommandView || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Result || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Image) this.scrollToBottom(); WI.quickConsole.needsLayout(); if (this._pendingMessagesForSessionOrGroup.size) this.renderPendingMessagesSoon(); } renderPendingMessagesSoon() { if (this._scheduledRenderIdentifier) return; this._scheduledRenderIdentifier = requestAnimationFrame(() => this.renderPendingMessages()); } _didRenderConsoleMessageView(messageView) { var type = messageView instanceof WI.ConsoleCommandView ? null : messageView.message.type; if (type === WI.ConsoleMessage.MessageType.EndGroup) { var parentGroup = this._currentSessionOrGroup.parentGroup; if (parentGroup) this._currentSessionOrGroup = parentGroup; } else { if (type === WI.ConsoleMessage.MessageType.StartGroup || type === WI.ConsoleMessage.MessageType.StartGroupCollapsed) { var group = new WI.ConsoleGroup(this._currentSessionOrGroup); var groupElement = group.render(messageView); this._currentSessionOrGroup.append(groupElement); this._currentSessionOrGroup = group; } else this._currentSessionOrGroup.addMessageView(messageView); } if (this.delegate && typeof this.delegate.didAppendConsoleMessageView === "function") this.delegate.didAppendConsoleMessageView(messageView); } }; WI.JavaScriptLogViewController.CachedPropertiesDuration = 30_000; WI.JavaScriptLogViewController.IgnoredRepeatCount = Symbol("ignored-repeat-count");