blob: 26c162df62d2feb6cdee188d9ebe9d3ef03d205c [file] [log] [blame]
// Copyright 2015 Google Inc. 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) {
this.INFO = window.debugHub.LOG_INFO;
this.WARNING = window.debugHub.LOG_WARNING;
this.ERROR = window.debugHub.LOG_ERROR;
this.ERROR_REPORT = window.debugHub.LOG_ERROR_REPORT;
this.FATAL = window.debugHub.LOG_FATAL;
this.INTERACTIVE = window.debugHub.LOG_FATAL + 1;
// 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;
}
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.style.whiteSpace = 'pre';
var text = document.createTextNode(message);
if (severity == this.INFO) {
elem.style.color = '#A0FFA0';
} else if (severity == this.WARNING) {
elem.style.color = '#FFFF80';
} else if (severity == this.ERROR ||
severity == this.ERROR_REPORT ||
severity == this.FATAL) {
elem.style.color = '#FF9080';
} else {
elem.style.color = '#FFFFFF';
}
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.pageUp = function() {
this.displayPos += this.SCROLL_SIZE;
var max = this.buffer.size - this.DISPLAY_AT_HEAD;
if (this.displayPos > max) {
this.displayPos = max;
}
this.displayMessages();
}
MessageLog.prototype.pageDown = function() {
this.displayPos -= this.SCROLL_SIZE;
if (this.displayPos < 0) {
this.displayPos = 0;
}
this.displayMessages();
}
MessageLog.prototype.toHead = function() {
this.displayPos = this.buffer.size - this.DISPLAY_AT_HEAD;
this.displayMessages();
}
MessageLog.prototype.toTail = function() {
this.displayPos = 0;
this.displayMessages();
}