| // Copyright 2015 The Cobalt Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // The MessageLog class manages the storage and display of all log messages in | |
| // the debug console. | |
| // | |
| // Two primary components are used: | |
| // 1. A buffer containing all the messages, MessageBuffer. This typically | |
| // contains many more messages than can be displayed on a single screen. | |
| // 2. An HTML container filled with elements for each message. This container | |
| // is passed in to the MessageLog constructor. | |
| // | |
| // The MessageLog class creates a MessageBuffer object to contain the | |
| // messages, and populates the HTML container to display the paginated | |
| // messages. Each HTML element is not created until the corresponding message | |
| // needs to be displayed, so if the debug console is initially invisible, | |
| // no HTML nodes will be created until it becomes visible. | |
| // Constructor for a single entry in the message buffer. | |
| function BufferEntry(severity, message) { | |
| this.severity = severity; | |
| this.message = message; | |
| this.element = null; | |
| } | |
| // Constructor for the actual buffer used to store messages. | |
| // This is implemented as a simple circular buffer of BufferEntry objects. | |
| function MessageBuffer() { | |
| // The maximum number of entries that can be stored. Not all of these will | |
| // be simultaneously displayed. | |
| this.MAX_BUFFER_SIZE = 500; | |
| // Array of BufferEntry objects, used as a circular buffer. | |
| this.entries = []; | |
| // Current buffer insert position. | |
| this.insertPos = 0; | |
| // Current number of items in the buffer. | |
| this.size = 0; | |
| } | |
| // Add a new item to the message buffer at the current insert point. | |
| MessageBuffer.prototype.insert = function(bufferEntry) { | |
| this.entries[this.insertPos] = bufferEntry; | |
| if (this.size < this.MAX_BUFFER_SIZE) { | |
| this.size += 1; | |
| } | |
| // Increment insert position with wraparound. | |
| this.insertPos = (this.insertPos + 1) % this.MAX_BUFFER_SIZE; | |
| } | |
| // Get an item from the message buffer at a logical index, where 0 corresponds | |
| // to the last inserted item, and index N is the Nth last item to be inserted. | |
| MessageBuffer.prototype.get = function(index) { | |
| if (index >= this.size) { | |
| return null; | |
| } | |
| // The index parameter is relative to the current insert position. | |
| var offsetIndex = (this.insertPos - 1 - index); | |
| // Buffer wraparound | |
| while (offsetIndex < 0) { | |
| offsetIndex += this.MAX_BUFFER_SIZE; | |
| } | |
| offsetIndex = offsetIndex % this.MAX_BUFFER_SIZE; | |
| return this.entries[offsetIndex]; | |
| } | |
| // Constructor for the message log object itself. | |
| function MessageLog(messageContainer) { | |
| // Number of items to display on a single page. | |
| this.PAGE_SIZE = 50; | |
| // Number of items to scroll when the user pages up or down. | |
| this.SCROLL_SIZE = 20; | |
| // Number of lines to display when paging up to beginning of buffer. | |
| this.DISPLAY_AT_HEAD = 30; | |
| // The parent HTML element that holds the message elements. | |
| this.messageContainer = messageContainer; | |
| // The buffer that holds the message entries. | |
| this.buffer = new MessageBuffer(); | |
| // Index of the item at the bottom of the currently displayed page. | |
| this.displayPos = 0; | |
| // Whether the message log is currently visible. | |
| this.visible = false; | |
| } | |
| // Log levels defined by the 'level' property of LogEntry | |
| // https://chromedevtools.github.io/devtools-protocol/1-3/Log#type-LogEntry | |
| MessageLog.VERBOSE = "verbose"; | |
| MessageLog.INFO = "info"; | |
| MessageLog.WARNING = "warning"; | |
| MessageLog.ERROR = "error"; | |
| // Custom level used internally by the console. | |
| MessageLog.INTERACTIVE = "interactive"; | |
| // Prefix on severity for messages from the JS console. | |
| MessageLog.CONSOLE = '*'; | |
| MessageLog.prototype.setVisible = function(visible) { | |
| var wasVisible = this.visible; | |
| this.visible = visible; | |
| if (this.visible) { | |
| if (!wasVisible) { | |
| // Newly visible, display the messages on the current page. | |
| this.displayMessages(); | |
| } | |
| } else { | |
| // Invisible. In theory, we might want to clear any HTML elements here, | |
| // but in practice, because of deferred GC, it doesn't gain us anything, | |
| // and in fact seems to result in more HTML nodes most of the time. | |
| } | |
| } | |
| // Creates the HTML element for a single message. | |
| MessageLog.prototype.createMessageElement = function(severity, message) { | |
| // An empty message will result in an empty box occupying no space. | |
| // Insert a space into empty messages to preserve line formatting. | |
| if (message.length <= 0) { | |
| message = ' '; | |
| }; | |
| // Create the new text element with the message. | |
| var elem = document.createElement('div'); | |
| elem.className = 'message'; | |
| var text = document.createTextNode(message); | |
| if (severity.startsWith(MessageLog.CONSOLE)) { | |
| severity = severity.substr(MessageLog.CONSOLE.length); | |
| elem.classList.add('console'); | |
| } | |
| elem.classList.add(severity); | |
| elem.appendChild(text); | |
| return elem; | |
| } | |
| // Adds a new message to the log. Stores it in the buffer, and updates the | |
| // display. | |
| MessageLog.prototype.addMessage = function(severity, message) { | |
| // Split into lines and add one entry per line. | |
| // This makes it much simpler to manage layout with pagination. | |
| var lines = message.split('\n'); | |
| var nLines = lines.length; | |
| // Ignore an empty line at the end - it just indicates a trailing newline. | |
| if (nLines >= 2 && lines[nLines - 1] == '') { | |
| nLines -= 1; | |
| } | |
| for (var l = 0; l < nLines; l++) { | |
| // Create the buffer entry and add it to the buffer. | |
| // Don't create the HTML element yet - defer until display. | |
| var newEntry = new BufferEntry(severity, lines[l]); | |
| this.buffer.insert(newEntry); | |
| } | |
| this.displayPos = 0; | |
| this.displayMessages(); | |
| } | |
| // Completely repopulates the current message container, taking into account | |
| // the current pagination settings. | |
| MessageLog.prototype.displayMessages = function() { | |
| // Don't do anything if not currently visible. | |
| if (!this.visible) { | |
| return; | |
| } | |
| // Remove all currently displayed message elements and repopulate. | |
| while (this.messageContainer.firstChild) { | |
| this.messageContainer.removeChild(this.messageContainer.firstChild); | |
| } | |
| for (var n = this.PAGE_SIZE; n >= 0; n--) { | |
| var idx = n + this.displayPos; | |
| var bufferEntry = this.buffer.get(idx); | |
| if (bufferEntry) { | |
| // Create the HTML element if necessary. | |
| if (!bufferEntry.element) { | |
| bufferEntry.element = this.createMessageElement(bufferEntry.severity, | |
| bufferEntry.message); | |
| } | |
| this.messageContainer.appendChild(bufferEntry.element); | |
| } | |
| } | |
| } | |
| MessageLog.prototype.scrollUp = function(size) { | |
| this.displayPos += size; | |
| var max = this.buffer.size - this.DISPLAY_AT_HEAD; | |
| if (this.displayPos > max) { | |
| this.displayPos = max; | |
| } | |
| this.displayMessages(); | |
| } | |
| MessageLog.prototype.scrollDown = function(size) { | |
| this.displayPos -= size; | |
| if (this.displayPos < 0) { | |
| this.displayPos = 0; | |
| } | |
| this.displayMessages(); | |
| } | |
| MessageLog.prototype.pageUp = function() { | |
| this.scrollUp(this.SCROLL_SIZE); | |
| } | |
| MessageLog.prototype.pageDown = function() { | |
| this.scrollDown(this.SCROLL_SIZE); | |
| } | |
| MessageLog.prototype.toHead = function() { | |
| this.displayPos = this.buffer.size - this.DISPLAY_AT_HEAD; | |
| this.displayMessages(); | |
| } | |
| MessageLog.prototype.toTail = function() { | |
| this.displayPos = 0; | |
| this.displayMessages(); | |
| } | |