Path: blob/trunk/third_party/closure/goog/debug/debugwindow.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Definition of the DebugWindow class. Please minimize16* dependencies this file has on other closure classes as any dependency it17* takes won't be able to use the logging infrastructure.18*19*/2021goog.provide('goog.debug.DebugWindow');2223goog.require('goog.debug.HtmlFormatter');24goog.require('goog.debug.LogManager');25goog.require('goog.debug.Logger');26goog.require('goog.dom.safe');27goog.require('goog.html.SafeHtml');28goog.require('goog.html.SafeStyleSheet');29goog.require('goog.string.Const');30goog.require('goog.structs.CircularBuffer');31goog.require('goog.userAgent');32333435/**36* Provides a debug DebugWindow that is bound to the goog.debug.Logger.37* It handles log messages and writes them to the DebugWindow. This doesn't38* provide a lot of functionality that the old Gmail logging infrastructure39* provided like saving debug logs for exporting to the server. Now that we40* have an event-based logging infrastructure, we can encapsulate that41* functionality in a separate class.42*43* @constructor44* @param {string=} opt_identifier Identifier for this logging class.45* @param {string=} opt_prefix Prefix prepended to messages.46*/47goog.debug.DebugWindow = function(opt_identifier, opt_prefix) {48/**49* Identifier for this logging class50* @protected {string}51*/52this.identifier = opt_identifier || '';5354/**55* Array used to buffer log output56* @protected {!Array<!goog.html.SafeHtml>}57*/58this.outputBuffer = [];5960/**61* Optional prefix to be prepended to error strings62* @private {string}63*/64this.prefix_ = opt_prefix || '';6566/**67* Buffer for saving the last 1000 messages68* @private {!goog.structs.CircularBuffer}69*/70this.savedMessages_ =71new goog.structs.CircularBuffer(goog.debug.DebugWindow.MAX_SAVED);7273/**74* Save the publish handler so it can be removed75* @private {!Function}76*/77this.publishHandler_ = goog.bind(this.addLogRecord, this);7879/**80* Formatter for formatted output81* @private {goog.debug.Formatter}82*/83this.formatter_ = new goog.debug.HtmlFormatter(this.prefix_);8485/**86* Loggers that we shouldn't output87* @private {!Object}88*/89this.filteredLoggers_ = {};9091// enable by default92this.setCapturing(true);9394/**95* Whether we are currently enabled. When the DebugWindow is enabled, it tries96* to keep its window open. When it's disabled, it can still be capturing log97* output if, but it won't try to write them to the DebugWindow window until98* it's enabled.99* @private {boolean}100*/101this.enabled_ = goog.debug.DebugWindow.isEnabled(this.identifier);102103// timer to save the DebugWindow's window position in a cookie104goog.global.setInterval(goog.bind(this.saveWindowPositionSize_, this), 7500);105};106107108/**109* Max number of messages to be saved110* @type {number}111*/112goog.debug.DebugWindow.MAX_SAVED = 500;113114115/**116* How long to keep the cookies for in milliseconds117* @type {number}118*/119goog.debug.DebugWindow.COOKIE_TIME = 30 * 24 * 60 * 60 * 1000; // 30-days120121122/**123* HTML string printed when the debug window opens124* @type {string}125* @protected126*/127goog.debug.DebugWindow.prototype.welcomeMessage = 'LOGGING';128129130/**131* Whether to force enable the window on a severe log.132* @type {boolean}133* @private134*/135goog.debug.DebugWindow.prototype.enableOnSevere_ = false;136137138/**139* Reference to debug window140* @type {Window}141* @protected142*/143goog.debug.DebugWindow.prototype.win = null;144145146/**147* In the process of opening the window148* @type {boolean}149* @private150*/151goog.debug.DebugWindow.prototype.winOpening_ = false;152153154/**155* Whether we are currently capturing logger output.156*157* @type {boolean}158* @private159*/160goog.debug.DebugWindow.prototype.isCapturing_ = false;161162163/**164* Whether we already showed an alert that the DebugWindow was blocked.165* @type {boolean}166* @private167*/168goog.debug.DebugWindow.showedBlockedAlert_ = false;169170171/**172* Reference to timeout used to buffer the output stream.173* @type {?number}174* @private175*/176goog.debug.DebugWindow.prototype.bufferTimeout_ = null;177178179/**180* Timestamp for the last time the log was written to.181* @protected {number}182*/183goog.debug.DebugWindow.prototype.lastCall = goog.now();184185186/**187* Sets the welcome message shown when the window is first opened or reset.188*189* @param {string} msg An HTML string.190*/191goog.debug.DebugWindow.prototype.setWelcomeMessage = function(msg) {192this.welcomeMessage = msg;193};194195196/**197* Initializes the debug window.198*/199goog.debug.DebugWindow.prototype.init = function() {200if (this.enabled_) {201this.openWindow_();202}203};204205206/**207* Whether the DebugWindow is enabled. When the DebugWindow is enabled, it208* tries to keep its window open and logs all messages to the window. When the209* DebugWindow is disabled, it stops logging messages to its window.210*211* @return {boolean} Whether the DebugWindow is enabled.212*/213goog.debug.DebugWindow.prototype.isEnabled = function() {214return this.enabled_;215};216217218/**219* Sets whether the DebugWindow is enabled. When the DebugWindow is enabled, it220* tries to keep its window open and log all messages to the window. When the221* DebugWindow is disabled, it stops logging messages to its window. The222* DebugWindow also saves this state to a cookie so that it's persisted across223* application refreshes.224* @param {boolean} enable Whether the DebugWindow is enabled.225*/226goog.debug.DebugWindow.prototype.setEnabled = function(enable) {227this.enabled_ = enable;228229if (this.enabled_) {230this.openWindow_();231}232233this.setCookie_('enabled', enable ? '1' : '0');234};235236237/**238* Sets whether the debug window should be force enabled when a severe log is239* encountered.240* @param {boolean} enableOnSevere Whether to enable on severe logs..241*/242goog.debug.DebugWindow.prototype.setForceEnableOnSevere = function(243enableOnSevere) {244this.enableOnSevere_ = enableOnSevere;245};246247248/**249* Whether we are currently capturing logger output.250* @return {boolean} whether we are currently capturing logger output.251*/252goog.debug.DebugWindow.prototype.isCapturing = function() {253return this.isCapturing_;254};255256257/**258* Sets whether we are currently capturing logger output.259* @param {boolean} capturing Whether to capture logger output.260*/261goog.debug.DebugWindow.prototype.setCapturing = function(capturing) {262if (capturing == this.isCapturing_) {263return;264}265this.isCapturing_ = capturing;266267// attach or detach handler from the root logger268var rootLogger = goog.debug.LogManager.getRoot();269if (capturing) {270rootLogger.addHandler(this.publishHandler_);271} else {272rootLogger.removeHandler(this.publishHandler_);273}274};275276277/**278* Gets the formatter for outputting to the debug window. The default formatter279* is an instance of goog.debug.HtmlFormatter280* @return {goog.debug.Formatter} The formatter in use.281*/282goog.debug.DebugWindow.prototype.getFormatter = function() {283return this.formatter_;284};285286287/**288* Sets the formatter for outputting to the debug window.289* @param {goog.debug.Formatter} formatter The formatter to use.290*/291goog.debug.DebugWindow.prototype.setFormatter = function(formatter) {292this.formatter_ = formatter;293};294295296/**297* Adds a separator to the debug window.298*/299goog.debug.DebugWindow.prototype.addSeparator = function() {300this.write_(goog.html.SafeHtml.create('hr'));301};302303304/**305* @return {boolean} Whether there is an active window.306*/307goog.debug.DebugWindow.prototype.hasActiveWindow = function() {308return !!this.win && !this.win.closed;309};310311312/**313* Clears the contents of the debug window314* @protected315*/316goog.debug.DebugWindow.prototype.clear = function() {317this.savedMessages_.clear();318if (this.hasActiveWindow()) {319this.writeInitialDocument();320}321};322323324/**325* Adds a log record.326* @param {goog.debug.LogRecord} logRecord the LogRecord.327*/328goog.debug.DebugWindow.prototype.addLogRecord = function(logRecord) {329if (this.filteredLoggers_[logRecord.getLoggerName()]) {330return;331}332var html = this.formatter_.formatRecordAsHtml(logRecord);333this.write_(html);334if (this.enableOnSevere_ &&335logRecord.getLevel().value >= goog.debug.Logger.Level.SEVERE.value) {336this.setEnabled(true);337}338};339340341/**342* Writes a message to the log, possibly opening up the window if it's enabled,343* or saving it if it's disabled.344* @param {!goog.html.SafeHtml} html The HTML to write.345* @private346*/347goog.debug.DebugWindow.prototype.write_ = function(html) {348// If the logger is enabled, open window and write html message to log349// otherwise save it350if (this.enabled_) {351this.openWindow_();352this.savedMessages_.add(html);353this.writeToLog_(html);354} else {355this.savedMessages_.add(html);356}357};358359360/**361* Write to the buffer. If a message hasn't been sent for more than 750ms just362* write, otherwise delay for a minimum of 250ms.363* @param {!goog.html.SafeHtml} html HTML to post to the log.364* @private365*/366goog.debug.DebugWindow.prototype.writeToLog_ = function(html) {367this.outputBuffer.push(html);368goog.global.clearTimeout(this.bufferTimeout_);369370if (goog.now() - this.lastCall > 750) {371this.writeBufferToLog();372} else {373this.bufferTimeout_ =374goog.global.setTimeout(goog.bind(this.writeBufferToLog, this), 250);375}376};377378379/**380* Write to the log and maybe scroll into view.381* @protected382*/383goog.debug.DebugWindow.prototype.writeBufferToLog = function() {384this.lastCall = goog.now();385if (this.hasActiveWindow()) {386var body = this.win.document.body;387var scroll =388body && body.scrollHeight - (body.scrollTop + body.clientHeight) <= 100;389390goog.dom.safe.documentWrite(391this.win.document, goog.html.SafeHtml.concat(this.outputBuffer));392this.outputBuffer.length = 0;393394if (scroll) {395this.win.scrollTo(0, 1000000);396}397}398};399400401/**402* Writes all saved messages to the DebugWindow.403* @protected404*/405goog.debug.DebugWindow.prototype.writeSavedMessages = function() {406var messages = this.savedMessages_.getValues();407for (var i = 0; i < messages.length; i++) {408this.writeToLog_(messages[i]);409}410};411412413/**414* Opens the debug window if it is not already referenced415* @private416*/417goog.debug.DebugWindow.prototype.openWindow_ = function() {418if (this.hasActiveWindow() || this.winOpening_) {419return;420}421422var winpos = this.getCookie_('dbg', '0,0,800,500').split(',');423var x = Number(winpos[0]);424var y = Number(winpos[1]);425var w = Number(winpos[2]);426var h = Number(winpos[3]);427428this.winOpening_ = true;429this.win = window.open(430'', this.getWindowName_(), 'width=' + w + ',height=' + h +431',toolbar=no,resizable=yes,' +432'scrollbars=yes,left=' + x + ',top=' + y + ',status=no,screenx=' + x +433',screeny=' + y);434435if (!this.win) {436if (!goog.debug.DebugWindow.showedBlockedAlert_) {437// only show this once438alert('Logger popup was blocked');439goog.debug.DebugWindow.showedBlockedAlert_ = true;440}441}442443this.winOpening_ = false;444445if (this.win) {446this.writeInitialDocument();447}448};449450451/**452* Gets a valid window name for the debug window. Replaces invalid characters in453* IE.454* @return {string} Valid window name.455* @private456*/457goog.debug.DebugWindow.prototype.getWindowName_ = function() {458return goog.userAgent.IE ? this.identifier.replace(/[\s\-\.\,]/g, '_') :459this.identifier;460};461462463/**464* @return {!goog.html.SafeStyleSheet} The stylesheet, for inclusion in the465* initial HTML.466*/467goog.debug.DebugWindow.prototype.getStyleRules = function() {468return goog.html.SafeStyleSheet.fromConstant(469goog.string.Const.from(470'*{font:normal 14px monospace;}' +471'.dbg-sev{color:#F00}' +472'.dbg-w{color:#E92}' +473'.dbg-sh{background-color:#fd4;font-weight:bold;color:#000}' +474'.dbg-i{color:#666}' +475'.dbg-f{color:#999}' +476'.dbg-ev{color:#0A0}' +477'.dbg-m{color:#990}'));478};479480481/**482* Writes the initial HTML of the debug window.483* @protected484*/485goog.debug.DebugWindow.prototype.writeInitialDocument = function() {486if (!this.hasActiveWindow()) {487return;488}489490this.win.document.open();491492var div = goog.html.SafeHtml.create(493'div', {494'class': 'dbg-ev',495'style': goog.string.Const.from('text-align:center;')496},497goog.html.SafeHtml.concat(498this.welcomeMessage, goog.html.SafeHtml.BR,499goog.html.SafeHtml.create(500'small', {}, 'Logger: ' + this.identifier)));501var html = goog.html.SafeHtml.concat(502goog.html.SafeHtml.createStyle(this.getStyleRules()),503goog.html.SafeHtml.create('hr'), div, goog.html.SafeHtml.create('hr'));504505this.writeToLog_(html);506this.writeSavedMessages();507};508509510/**511* Save persistent data (using cookies) for 1 month (cookie specific to this512* logger object).513* @param {string} key Data name.514* @param {string} value Data value.515* @private516*/517goog.debug.DebugWindow.prototype.setCookie_ = function(key, value) {518var fullKey = goog.debug.DebugWindow.getCookieKey_(this.identifier, key);519document.cookie = fullKey + '=' + encodeURIComponent(value) +520';path=/;expires=' +521(new Date(goog.now() + goog.debug.DebugWindow.COOKIE_TIME)).toUTCString();522};523524525/**526* Retrieve data (using cookies).527* @param {string} key Data name.528* @param {string=} opt_default Optional default value if cookie doesn't exist.529* @return {string} Cookie value.530* @private531*/532goog.debug.DebugWindow.prototype.getCookie_ = function(key, opt_default) {533return goog.debug.DebugWindow.getCookieValue_(534this.identifier, key, opt_default);535};536537538/**539* Creates a valid cookie key name which is scoped to the given identifier.540* Substitutes all occurrences of invalid cookie name characters (whitespace,541* ';', and '=') with '_', which is a valid and readable alternative.542* @see goog.net.Cookies#isValidName543* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>544* @param {string} identifier Identifier for logging class.545* @param {string} key Data name.546* @return {string} Cookie key name.547* @private548*/549goog.debug.DebugWindow.getCookieKey_ = function(identifier, key) {550var fullKey = key + identifier;551return fullKey.replace(/[;=\s]/g, '_');552};553554555/**556* Retrieve data (using cookies).557* @param {string} identifier Identifier for logging class.558* @param {string} key Data name.559* @param {string=} opt_default Optional default value if cookie doesn't exist.560* @return {string} Cookie value.561* @private562*/563goog.debug.DebugWindow.getCookieValue_ = function(564identifier, key, opt_default) {565var fullKey = goog.debug.DebugWindow.getCookieKey_(identifier, key);566var cookie = String(document.cookie);567var start = cookie.indexOf(fullKey + '=');568if (start != -1) {569var end = cookie.indexOf(';', start);570return decodeURIComponent(571cookie.substring(572start + fullKey.length + 1, end == -1 ? cookie.length : end));573} else {574return opt_default || '';575}576};577578579/**580* @param {string} identifier Identifier for logging class.581* @return {boolean} Whether the DebugWindow is enabled.582*/583goog.debug.DebugWindow.isEnabled = function(identifier) {584return goog.debug.DebugWindow.getCookieValue_(identifier, 'enabled') == '1';585};586587588/**589* Saves the window position size to a cookie590* @private591*/592goog.debug.DebugWindow.prototype.saveWindowPositionSize_ = function() {593if (!this.hasActiveWindow()) {594return;595}596var x = this.win.screenX || this.win.screenLeft || 0;597var y = this.win.screenY || this.win.screenTop || 0;598var w = this.win.outerWidth || 800;599var h = this.win.outerHeight || 500;600this.setCookie_('dbg', x + ',' + y + ',' + w + ',' + h);601};602603604/**605* Adds a logger name to be filtered.606* @param {string} loggerName the logger name to add.607*/608goog.debug.DebugWindow.prototype.addFilter = function(loggerName) {609this.filteredLoggers_[loggerName] = 1;610};611612613/**614* Removes a logger name to be filtered.615* @param {string} loggerName the logger name to remove.616*/617goog.debug.DebugWindow.prototype.removeFilter = function(loggerName) {618delete this.filteredLoggers_[loggerName];619};620621622/**623* Modify the size of the circular buffer. Allows the log to retain more624* information while the window is closed.625* @param {number} size New size of the circular buffer.626*/627goog.debug.DebugWindow.prototype.resetBufferWithNewSize = function(size) {628if (size > 0 && size < 50000) {629this.clear();630this.savedMessages_ = new goog.structs.CircularBuffer(size);631}632};633634635