Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Simple logger that logs to the window console if available.
|
||||
*
|
||||
* Has an autoInstall option which can be put into initialization code, which
|
||||
* will start logging if "Debug=true" is in document.location.href
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Console');
|
||||
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.TextFormatter');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create and install a log handler that logs to window.console if available
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.Console = function() {
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
|
||||
/**
|
||||
* Formatter for formatted output.
|
||||
* @type {!goog.debug.TextFormatter}
|
||||
* @private
|
||||
*/
|
||||
this.formatter_ = new goog.debug.TextFormatter();
|
||||
this.formatter_.showAbsoluteTime = false;
|
||||
this.formatter_.showExceptionText = false;
|
||||
// The console logging methods automatically append a newline.
|
||||
this.formatter_.appendNewline = false;
|
||||
|
||||
this.isCapturing_ = false;
|
||||
this.logBuffer_ = '';
|
||||
|
||||
/**
|
||||
* Loggers that we shouldn't output.
|
||||
* @type {!Object<boolean>}
|
||||
* @private
|
||||
*/
|
||||
this.filteredLoggers_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text formatter used by this console
|
||||
* @return {!goog.debug.TextFormatter} The text formatter.
|
||||
*/
|
||||
goog.debug.Console.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.Console.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
this.logBuffer = '';
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord The log entry.
|
||||
*/
|
||||
goog.debug.Console.prototype.addLogRecord = function(logRecord) {
|
||||
|
||||
// Check to see if the log record is filtered or not.
|
||||
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var record = this.formatter_.formatRecord(logRecord);
|
||||
var console = goog.debug.Console.console_;
|
||||
if (console) {
|
||||
switch (logRecord.getLevel()) {
|
||||
case goog.debug.Logger.Level.SHOUT:
|
||||
goog.debug.Console.logToConsole_(console, 'info', record);
|
||||
break;
|
||||
case goog.debug.Logger.Level.SEVERE:
|
||||
goog.debug.Console.logToConsole_(console, 'error', record);
|
||||
break;
|
||||
case goog.debug.Logger.Level.WARNING:
|
||||
goog.debug.Console.logToConsole_(console, 'warn', record);
|
||||
break;
|
||||
default:
|
||||
goog.debug.Console.logToConsole_(console, 'debug', record);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this.logBuffer_ += record;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to add.
|
||||
*/
|
||||
goog.debug.Console.prototype.addFilter = function(loggerName) {
|
||||
this.filteredLoggers_[loggerName] = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to remove.
|
||||
*/
|
||||
goog.debug.Console.prototype.removeFilter = function(loggerName) {
|
||||
delete this.filteredLoggers_[loggerName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global console logger instance
|
||||
* @type {goog.debug.Console}
|
||||
*/
|
||||
goog.debug.Console.instance = null;
|
||||
|
||||
|
||||
/**
|
||||
* The console to which to log. This is a property so it can be mocked out in
|
||||
* this unit test for goog.debug.Console. Using goog.global, as console might be
|
||||
* used in window-less contexts.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Console.console_ = goog.global['console'];
|
||||
|
||||
|
||||
/**
|
||||
* Sets the console to which to log.
|
||||
* @param {!Object} console The console to which to log.
|
||||
*/
|
||||
goog.debug.Console.setConsole = function(console) {
|
||||
goog.debug.Console.console_ = console;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Install the console and start capturing if "Debug=true" is in the page URL
|
||||
*/
|
||||
goog.debug.Console.autoInstall = function() {
|
||||
if (!goog.debug.Console.instance) {
|
||||
goog.debug.Console.instance = new goog.debug.Console();
|
||||
}
|
||||
|
||||
if (goog.global.location &&
|
||||
goog.global.location.href.indexOf('Debug=true') != -1) {
|
||||
goog.debug.Console.instance.setCapturing(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show an alert with all of the captured debug information.
|
||||
* Information is only captured if console is not available
|
||||
*/
|
||||
goog.debug.Console.show = function() {
|
||||
alert(goog.debug.Console.instance.logBuffer_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the record to the console using the given function. If the function is
|
||||
* not available on the console object, the log function is used instead.
|
||||
* @param {!Object} console The console object.
|
||||
* @param {string} fnName The name of the function to use.
|
||||
* @param {string} record The record to log.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Console.logToConsole_ = function(console, fnName, record) {
|
||||
if (console[fnName]) {
|
||||
console[fnName](record);
|
||||
} else {
|
||||
console.log(record);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.Console
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.ConsoleTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2011 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.ConsoleTest');
|
||||
goog.setTestOnly('goog.debug.ConsoleTest');
|
||||
|
||||
goog.require('goog.debug.Console');
|
||||
goog.require('goog.debug.LogRecord');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var debugConsole;
|
||||
var mockConsole;
|
||||
var loggerName1;
|
||||
var logRecord1;
|
||||
var loggerName2;
|
||||
var logRecord2;
|
||||
var loggerName3;
|
||||
var logRecord3;
|
||||
|
||||
function setUp() {
|
||||
debugConsole = new goog.debug.Console();
|
||||
|
||||
// Set up a recorder for mockConsole.log
|
||||
mockConsole = { log: goog.testing.recordFunction() };
|
||||
goog.debug.Console.console_ = mockConsole;
|
||||
|
||||
// Test logger 1.
|
||||
loggerName1 = 'this.is.a.logger';
|
||||
logRecord1 = new goog.debug.LogRecord(goog.debug.Logger.Level.INFO,
|
||||
'this is a statement', loggerName1);
|
||||
|
||||
// Test logger 2.
|
||||
loggerName2 = 'name.of.logger';
|
||||
logRecord2 = new goog.debug.LogRecord(goog.debug.Logger.Level.WARNING,
|
||||
'hey, this is a warning', loggerName2);
|
||||
|
||||
// Test logger 3.
|
||||
loggerName3 = 'third.logger';
|
||||
logRecord3 = new goog.debug.LogRecord(goog.debug.Logger.Level.SEVERE,
|
||||
'seriously, this statement is serious', loggerName3);
|
||||
}
|
||||
|
||||
function testLoggingWithSimpleConsole() {
|
||||
// Make sure all messages use the log function.
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(9, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testLoggingWithInfoSupported() {
|
||||
// Make sure the log function is the default when only 'info' is available.
|
||||
mockConsole['info'] = goog.testing.recordFunction();
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(1, mockConsole.info.getCallCount());
|
||||
assertEquals(8, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testLoggingWithErrorSupported() {
|
||||
// Make sure the log function is the default when only 'error' is available.
|
||||
mockConsole['error'] = goog.testing.recordFunction();
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(1, mockConsole.error.getCallCount());
|
||||
assertEquals(8, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testLoggingWithWarningSupported() {
|
||||
// Make sure the log function is the default when only 'warn' is available.
|
||||
mockConsole['warn'] = goog.testing.recordFunction();
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(1, mockConsole.warn.getCallCount());
|
||||
assertEquals(8, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testLoggingWithDebugSupported() {
|
||||
// Make sure the log function is the default when only 'debug' is available.
|
||||
mockConsole['debug'] = goog.testing.recordFunction();
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(6, mockConsole.debug.getCallCount());
|
||||
assertEquals(3, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testLoggingWithEverythingSupported() {
|
||||
mockConsole['info'] = goog.testing.recordFunction();
|
||||
mockConsole['error'] = goog.testing.recordFunction();
|
||||
mockConsole['warn'] = goog.testing.recordFunction();
|
||||
mockConsole['debug'] = goog.testing.recordFunction();
|
||||
logAtAllLevels('test message');
|
||||
assertEquals(1, mockConsole.info.getCallCount());
|
||||
assertEquals(1, mockConsole.error.getCallCount());
|
||||
assertEquals(1, mockConsole.warn.getCallCount());
|
||||
assertEquals(6, mockConsole.debug.getCallCount());
|
||||
}
|
||||
|
||||
function testAddLogRecordWithoutFilters() {
|
||||
// Make sure none are filtered.
|
||||
debugConsole.addLogRecord(logRecord1);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord2);
|
||||
assertEquals(2, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord3);
|
||||
assertEquals(3, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testAddLogRecordWithOneFilter() {
|
||||
// Filter #2 and make sure the filtering is correct for all records.
|
||||
debugConsole.addFilter(loggerName2);
|
||||
debugConsole.addLogRecord(logRecord1);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord2);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord3);
|
||||
assertEquals(2, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testAddLogRecordWithMoreThanOneFilter() {
|
||||
// Filter #1 and #3 and check.
|
||||
debugConsole.addFilter(loggerName1);
|
||||
debugConsole.addFilter(loggerName3);
|
||||
debugConsole.addLogRecord(logRecord1);
|
||||
assertEquals(0, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord2);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord3);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testAddLogRecordWithAddAndRemoveFilter() {
|
||||
debugConsole.addFilter(loggerName1);
|
||||
debugConsole.addFilter(loggerName2);
|
||||
debugConsole.removeFilter(loggerName1);
|
||||
debugConsole.removeFilter(loggerName2);
|
||||
debugConsole.addLogRecord(logRecord1);
|
||||
assertEquals(1, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord2);
|
||||
assertEquals(2, mockConsole.log.getCallCount());
|
||||
debugConsole.addLogRecord(logRecord3);
|
||||
assertEquals(3, mockConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
function testSetConsole() {
|
||||
var fakeConsole = {log: goog.testing.recordFunction()};
|
||||
|
||||
logAtLevel(goog.debug.Logger.Level.INFO, 'test message 1');
|
||||
logAtAllLevels('test message 1');
|
||||
assertEquals(0, fakeConsole.log.getCallCount());
|
||||
|
||||
goog.debug.Console.setConsole(fakeConsole);
|
||||
|
||||
logAtLevel(goog.debug.Logger.Level.INFO, 'test message 2');
|
||||
assertEquals(1, fakeConsole.log.getCallCount());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logs the message at all log levels.
|
||||
* @param {string} message The message to log.
|
||||
*/
|
||||
function logAtAllLevels(message) {
|
||||
logAtLevel(goog.debug.Logger.Level.SHOUT, message);
|
||||
logAtLevel(goog.debug.Logger.Level.SEVERE, message);
|
||||
logAtLevel(goog.debug.Logger.Level.WARNING, message);
|
||||
logAtLevel(goog.debug.Logger.Level.INFO, message);
|
||||
logAtLevel(goog.debug.Logger.Level.CONFIG, message);
|
||||
logAtLevel(goog.debug.Logger.Level.FINE, message);
|
||||
logAtLevel(goog.debug.Logger.Level.FINER, message);
|
||||
logAtLevel(goog.debug.Logger.Level.FINEST, message);
|
||||
logAtLevel(goog.debug.Logger.Level.ALL, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record to the debug console.
|
||||
* @param {!goog.debug.Logger.Level} level The level at which to log.
|
||||
* @param {string} message The message to log.
|
||||
*/
|
||||
function logAtLevel(level, message) {
|
||||
debugConsole.addLogRecord(
|
||||
new goog.debug.LogRecord(level, message, loggerName1));
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Logging and debugging utilities.
|
||||
*
|
||||
* @see ../demos/debug.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.html.SafeUrl');
|
||||
goog.require('goog.html.uncheckedconversions');
|
||||
goog.require('goog.string.Const');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/** @define {boolean} Whether logging should be enabled. */
|
||||
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
|
||||
|
||||
|
||||
/**
|
||||
* Catches onerror events fired by windows and similar objects.
|
||||
* @param {function(Object)} logFunc The function to call with the error
|
||||
* information.
|
||||
* @param {boolean=} opt_cancel Whether to stop the error from reaching the
|
||||
* browser.
|
||||
* @param {Object=} opt_target Object that fires onerror events.
|
||||
*/
|
||||
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
|
||||
var target = opt_target || goog.global;
|
||||
var oldErrorHandler = target.onerror;
|
||||
var retVal = !!opt_cancel;
|
||||
|
||||
// Chrome interprets onerror return value backwards (http://crbug.com/92062)
|
||||
// until it was fixed in webkit revision r94061 (Webkit 535.3). This
|
||||
// workaround still needs to be skipped in Safari after the webkit change
|
||||
// gets pushed out in Safari.
|
||||
// See https://bugs.webkit.org/show_bug.cgi?id=67119
|
||||
if (goog.userAgent.WEBKIT &&
|
||||
!goog.userAgent.isVersionOrHigher('535.3')) {
|
||||
retVal = !retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* New onerror handler for this target. This onerror handler follows the spec
|
||||
* according to
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
|
||||
* The spec was changed in August 2013 to support receiving column information
|
||||
* and an error object for all scripts on the same origin or cross origin
|
||||
* scripts with the proper headers. See
|
||||
* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
|
||||
*
|
||||
* @param {string} message The error message. For cross-origin errors, this
|
||||
* will be scrubbed to just "Script error.". For new browsers that have
|
||||
* updated to follow the latest spec, errors that come from origins that
|
||||
* have proper cross origin headers will not be scrubbed.
|
||||
* @param {string} url The URL of the script that caused the error. The URL
|
||||
* will be scrubbed to "" for cross origin scripts unless the script has
|
||||
* proper cross origin headers and the browser has updated to the latest
|
||||
* spec.
|
||||
* @param {number} line The line number in the script that the error
|
||||
* occurred on.
|
||||
* @param {number=} opt_col The optional column number that the error
|
||||
* occurred on. Only browsers that have updated to the latest spec will
|
||||
* include this.
|
||||
* @param {Error=} opt_error The optional actual error object for this
|
||||
* error that should include the stack. Only browsers that have updated
|
||||
* to the latest spec will inlude this parameter.
|
||||
* @return {boolean} Whether to prevent the error from reaching the browser.
|
||||
*/
|
||||
target.onerror = function(message, url, line, opt_col, opt_error) {
|
||||
if (oldErrorHandler) {
|
||||
oldErrorHandler(message, url, line, opt_col, opt_error);
|
||||
}
|
||||
logFunc({
|
||||
message: message,
|
||||
fileName: url,
|
||||
line: line,
|
||||
col: opt_col,
|
||||
error: opt_error
|
||||
});
|
||||
return retVal;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string representing an object and all its properties.
|
||||
* @param {Object|null|undefined} obj Object to expose.
|
||||
* @param {boolean=} opt_showFn Show the functions as well as the properties,
|
||||
* default is false.
|
||||
* @return {string} The string representation of {@code obj}.
|
||||
*/
|
||||
goog.debug.expose = function(obj, opt_showFn) {
|
||||
if (typeof obj == 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
if (obj == null) {
|
||||
return 'NULL';
|
||||
}
|
||||
var str = [];
|
||||
|
||||
for (var x in obj) {
|
||||
if (!opt_showFn && goog.isFunction(obj[x])) {
|
||||
continue;
|
||||
}
|
||||
var s = x + ' = ';
|
||||
/** @preserveTry */
|
||||
try {
|
||||
s += obj[x];
|
||||
} catch (e) {
|
||||
s += '*** ' + e + ' ***';
|
||||
}
|
||||
str.push(s);
|
||||
}
|
||||
return str.join('\n');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string representing a given primitive or object, and for an
|
||||
* object, all its properties and nested objects. WARNING: If an object is
|
||||
* given, it and all its nested objects will be modified. To detect reference
|
||||
* cycles, this method identifies objects using goog.getUid() which mutates the
|
||||
* object.
|
||||
* @param {*} obj Object to expose.
|
||||
* @param {boolean=} opt_showFn Also show properties that are functions (by
|
||||
* default, functions are omitted).
|
||||
* @return {string} A string representation of {@code obj}.
|
||||
*/
|
||||
goog.debug.deepExpose = function(obj, opt_showFn) {
|
||||
var str = [];
|
||||
|
||||
var helper = function(obj, space, parentSeen) {
|
||||
var nestspace = space + ' ';
|
||||
var seen = new goog.structs.Set(parentSeen);
|
||||
|
||||
var indentMultiline = function(str) {
|
||||
return str.replace(/\n/g, '\n' + space);
|
||||
};
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
if (!goog.isDef(obj)) {
|
||||
str.push('undefined');
|
||||
} else if (goog.isNull(obj)) {
|
||||
str.push('NULL');
|
||||
} else if (goog.isString(obj)) {
|
||||
str.push('"' + indentMultiline(obj) + '"');
|
||||
} else if (goog.isFunction(obj)) {
|
||||
str.push(indentMultiline(String(obj)));
|
||||
} else if (goog.isObject(obj)) {
|
||||
if (seen.contains(obj)) {
|
||||
str.push('*** reference loop detected ***');
|
||||
} else {
|
||||
seen.add(obj);
|
||||
str.push('{');
|
||||
for (var x in obj) {
|
||||
if (!opt_showFn && goog.isFunction(obj[x])) {
|
||||
continue;
|
||||
}
|
||||
str.push('\n');
|
||||
str.push(nestspace);
|
||||
str.push(x + ' = ');
|
||||
helper(obj[x], nestspace, seen);
|
||||
}
|
||||
str.push('\n' + space + '}');
|
||||
}
|
||||
} else {
|
||||
str.push(obj);
|
||||
}
|
||||
} catch (e) {
|
||||
str.push('*** ' + e + ' ***');
|
||||
}
|
||||
};
|
||||
|
||||
helper(obj, '', new goog.structs.Set());
|
||||
return str.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively outputs a nested array as a string.
|
||||
* @param {Array<?>} arr The array.
|
||||
* @return {string} String representing nested array.
|
||||
*/
|
||||
goog.debug.exposeArray = function(arr) {
|
||||
var str = [];
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (goog.isArray(arr[i])) {
|
||||
str.push(goog.debug.exposeArray(arr[i]));
|
||||
} else {
|
||||
str.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return '[ ' + str.join(', ') + ' ]';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exposes an exception that has been caught by a try...catch and outputs the
|
||||
* error as HTML with a stack trace.
|
||||
* @param {Object} err Error object or string.
|
||||
* @param {Function=} opt_fn Optional function to start stack trace from.
|
||||
* @return {string} Details of exception, as HTML.
|
||||
*/
|
||||
goog.debug.exposeException = function(err, opt_fn) {
|
||||
var html = goog.debug.exposeExceptionAsHtml(err, opt_fn);
|
||||
return goog.html.SafeHtml.unwrap(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exposes an exception that has been caught by a try...catch and outputs the
|
||||
* error with a stack trace.
|
||||
* @param {Object} err Error object or string.
|
||||
* @param {Function=} opt_fn Optional function to start stack trace from.
|
||||
* @return {!goog.html.SafeHtml} Details of exception.
|
||||
*/
|
||||
goog.debug.exposeExceptionAsHtml = function(err, opt_fn) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var e = goog.debug.normalizeErrorObject(err);
|
||||
// Create the error message
|
||||
var viewSourceUrl = goog.debug.createViewSourceUrl_(e.fileName);
|
||||
var error = goog.html.SafeHtml.concat(
|
||||
goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
|
||||
'Message: ' + e.message + '\nUrl: '),
|
||||
goog.html.SafeHtml.create('a',
|
||||
{href: viewSourceUrl, target: '_new'}, e.fileName),
|
||||
goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
|
||||
'\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
|
||||
e.stack + '-> ' + '[end]\n\nJS stack traversal:\n' +
|
||||
goog.debug.getStacktrace(opt_fn) + '-> '));
|
||||
return error;
|
||||
} catch (e2) {
|
||||
return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
|
||||
'Exception trying to expose exception! You win, we lose. ' + e2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?string=} opt_fileName
|
||||
* @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
|
||||
* fileName.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.createViewSourceUrl_ = function(opt_fileName) {
|
||||
if (!goog.isDefAndNotNull(opt_fileName)) {
|
||||
opt_fileName = '';
|
||||
}
|
||||
if (!/^https?:\/\//i.test(opt_fileName)) {
|
||||
return goog.html.SafeUrl.fromConstant(
|
||||
goog.string.Const.from('sanitizedviewsrc'));
|
||||
}
|
||||
var sanitizedFileName = goog.html.SafeUrl.sanitize(opt_fileName);
|
||||
return goog.html.uncheckedconversions.
|
||||
safeUrlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),
|
||||
'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes the error/exception object between browsers.
|
||||
* @param {Object} err Raw error object.
|
||||
* @return {!Object} Normalized error object.
|
||||
*/
|
||||
goog.debug.normalizeErrorObject = function(err) {
|
||||
var href = goog.getObjectByName('window.location.href');
|
||||
if (goog.isString(err)) {
|
||||
return {
|
||||
'message': err,
|
||||
'name': 'Unknown error',
|
||||
'lineNumber': 'Not available',
|
||||
'fileName': href,
|
||||
'stack': 'Not available'
|
||||
};
|
||||
}
|
||||
|
||||
var lineNumber, fileName;
|
||||
var threwError = false;
|
||||
|
||||
try {
|
||||
lineNumber = err.lineNumber || err.line || 'Not available';
|
||||
} catch (e) {
|
||||
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
|
||||
// Message: Permission denied to get property UnnamedClass.lineNumber
|
||||
lineNumber = 'Not available';
|
||||
threwError = true;
|
||||
}
|
||||
|
||||
try {
|
||||
fileName = err.fileName || err.filename || err.sourceURL ||
|
||||
// $googDebugFname may be set before a call to eval to set the filename
|
||||
// that the eval is supposed to present.
|
||||
goog.global['$googDebugFname'] || href;
|
||||
} catch (e) {
|
||||
// Firefox 2 may also throw an error when accessing 'filename'.
|
||||
fileName = 'Not available';
|
||||
threwError = true;
|
||||
}
|
||||
|
||||
// The IE Error object contains only the name and the message.
|
||||
// The Safari Error object uses the line and sourceURL fields.
|
||||
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
|
||||
!err.message || !err.name) {
|
||||
return {
|
||||
'message': err.message || 'Not available',
|
||||
'name': err.name || 'UnknownError',
|
||||
'lineNumber': lineNumber,
|
||||
'fileName': fileName,
|
||||
'stack': err.stack || 'Not available'
|
||||
};
|
||||
}
|
||||
|
||||
// Standards error object
|
||||
return err;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an object to an Error if it's a String,
|
||||
* adds a stacktrace if there isn't one,
|
||||
* and optionally adds an extra message.
|
||||
* @param {Error|string} err the original thrown object or string.
|
||||
* @param {string=} opt_message optional additional message to add to the
|
||||
* error.
|
||||
* @return {!Error} If err is a string, it is used to create a new Error,
|
||||
* which is enhanced and returned. Otherwise err itself is enhanced
|
||||
* and returned.
|
||||
*/
|
||||
goog.debug.enhanceError = function(err, opt_message) {
|
||||
var error;
|
||||
if (typeof err == 'string') {
|
||||
error = Error(err);
|
||||
if (Error.captureStackTrace) {
|
||||
// Trim this function off the call stack, if we can.
|
||||
Error.captureStackTrace(error, goog.debug.enhanceError);
|
||||
}
|
||||
} else {
|
||||
error = err;
|
||||
}
|
||||
|
||||
if (!error.stack) {
|
||||
error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
|
||||
}
|
||||
if (opt_message) {
|
||||
// find the first unoccupied 'messageX' property
|
||||
var x = 0;
|
||||
while (error['message' + x]) {
|
||||
++x;
|
||||
}
|
||||
error['message' + x] = String(opt_message);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current stack trace. Simple and iterative - doesn't worry about
|
||||
* catching circular references or getting the args.
|
||||
* @param {number=} opt_depth Optional maximum depth to trace back to.
|
||||
* @return {string} A string with the function names of all functions in the
|
||||
* stack, separated by \n.
|
||||
* @suppress {es5Strict}
|
||||
*/
|
||||
goog.debug.getStacktraceSimple = function(opt_depth) {
|
||||
if (goog.STRICT_MODE_COMPATIBLE) {
|
||||
var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
|
||||
if (stack) {
|
||||
return stack;
|
||||
}
|
||||
// NOTE: browsers that have strict mode support also have native "stack"
|
||||
// properties. Fall-through for legacy browser support.
|
||||
}
|
||||
|
||||
var sb = [];
|
||||
var fn = arguments.callee.caller;
|
||||
var depth = 0;
|
||||
|
||||
while (fn && (!opt_depth || depth < opt_depth)) {
|
||||
sb.push(goog.debug.getFunctionName(fn));
|
||||
sb.push('()\n');
|
||||
/** @preserveTry */
|
||||
try {
|
||||
fn = fn.caller;
|
||||
} catch (e) {
|
||||
sb.push('[exception trying to get caller]\n');
|
||||
break;
|
||||
}
|
||||
depth++;
|
||||
if (depth >= goog.debug.MAX_STACK_DEPTH) {
|
||||
sb.push('[...long stack...]');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opt_depth && depth >= opt_depth) {
|
||||
sb.push('[...reached max depth limit...]');
|
||||
} else {
|
||||
sb.push('[end]');
|
||||
}
|
||||
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Max length of stack to try and output
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.MAX_STACK_DEPTH = 50;
|
||||
|
||||
|
||||
/**
|
||||
* @param {Function} fn The function to start getting the trace from.
|
||||
* @return {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.getNativeStackTrace_ = function(fn) {
|
||||
var tempErr = new Error();
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(tempErr, fn);
|
||||
return String(tempErr.stack);
|
||||
} else {
|
||||
// IE10, only adds stack traces when an exception is thrown.
|
||||
try {
|
||||
throw tempErr;
|
||||
} catch (e) {
|
||||
tempErr = e;
|
||||
}
|
||||
var stack = tempErr.stack;
|
||||
if (stack) {
|
||||
return String(stack);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current stack trace, either starting from the caller or starting
|
||||
* from a specified function that's currently on the call stack.
|
||||
* @param {Function=} opt_fn Optional function to start getting the trace from.
|
||||
* If not provided, defaults to the function that called this.
|
||||
* @return {string} Stack trace.
|
||||
* @suppress {es5Strict}
|
||||
*/
|
||||
goog.debug.getStacktrace = function(opt_fn) {
|
||||
var stack;
|
||||
if (goog.STRICT_MODE_COMPATIBLE) {
|
||||
// Try to get the stack trace from the environment if it is available.
|
||||
var contextFn = opt_fn || goog.debug.getStacktrace;
|
||||
stack = goog.debug.getNativeStackTrace_(contextFn);
|
||||
}
|
||||
if (!stack) {
|
||||
// NOTE: browsers that have strict mode support also have native "stack"
|
||||
// properties. This function will throw in strict mode.
|
||||
stack = goog.debug.getStacktraceHelper_(
|
||||
opt_fn || arguments.callee.caller, []);
|
||||
}
|
||||
return stack;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper for getStacktrace().
|
||||
* @param {Function} fn Function to start getting the trace from.
|
||||
* @param {Array<!Function>} visited List of functions visited so far.
|
||||
* @return {string} Stack trace starting from function fn.
|
||||
* @suppress {es5Strict}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.getStacktraceHelper_ = function(fn, visited) {
|
||||
var sb = [];
|
||||
|
||||
// Circular reference, certain functions like bind seem to cause a recursive
|
||||
// loop so we need to catch circular references
|
||||
if (goog.array.contains(visited, fn)) {
|
||||
sb.push('[...circular reference...]');
|
||||
|
||||
// Traverse the call stack until function not found or max depth is reached
|
||||
} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
|
||||
sb.push(goog.debug.getFunctionName(fn) + '(');
|
||||
var args = fn.arguments;
|
||||
// Args may be null for some special functions such as host objects or eval.
|
||||
for (var i = 0; args && i < args.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.push(', ');
|
||||
}
|
||||
var argDesc;
|
||||
var arg = args[i];
|
||||
switch (typeof arg) {
|
||||
case 'object':
|
||||
argDesc = arg ? 'object' : 'null';
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
argDesc = arg;
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
argDesc = String(arg);
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
argDesc = arg ? 'true' : 'false';
|
||||
break;
|
||||
|
||||
case 'function':
|
||||
argDesc = goog.debug.getFunctionName(arg);
|
||||
argDesc = argDesc ? argDesc : '[fn]';
|
||||
break;
|
||||
|
||||
case 'undefined':
|
||||
default:
|
||||
argDesc = typeof arg;
|
||||
break;
|
||||
}
|
||||
|
||||
if (argDesc.length > 40) {
|
||||
argDesc = argDesc.substr(0, 40) + '...';
|
||||
}
|
||||
sb.push(argDesc);
|
||||
}
|
||||
visited.push(fn);
|
||||
sb.push(')\n');
|
||||
/** @preserveTry */
|
||||
try {
|
||||
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
|
||||
} catch (e) {
|
||||
sb.push('[exception trying to get caller]\n');
|
||||
}
|
||||
|
||||
} else if (fn) {
|
||||
sb.push('[...long stack...]');
|
||||
} else {
|
||||
sb.push('[end]');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set a custom function name resolver.
|
||||
* @param {function(Function): string} resolver Resolves functions to their
|
||||
* names.
|
||||
*/
|
||||
goog.debug.setFunctionResolver = function(resolver) {
|
||||
goog.debug.fnNameResolver_ = resolver;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a function name
|
||||
* @param {Function} fn Function to get name of.
|
||||
* @return {string} Function's name.
|
||||
*/
|
||||
goog.debug.getFunctionName = function(fn) {
|
||||
if (goog.debug.fnNameCache_[fn]) {
|
||||
return goog.debug.fnNameCache_[fn];
|
||||
}
|
||||
if (goog.debug.fnNameResolver_) {
|
||||
var name = goog.debug.fnNameResolver_(fn);
|
||||
if (name) {
|
||||
goog.debug.fnNameCache_[fn] = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristically determine function name based on code.
|
||||
var functionSource = String(fn);
|
||||
if (!goog.debug.fnNameCache_[functionSource]) {
|
||||
var matches = /function ([^\(]+)/.exec(functionSource);
|
||||
if (matches) {
|
||||
var method = matches[1];
|
||||
goog.debug.fnNameCache_[functionSource] = method;
|
||||
} else {
|
||||
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
|
||||
}
|
||||
}
|
||||
|
||||
return goog.debug.fnNameCache_[functionSource];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes whitespace visible by replacing it with printable characters.
|
||||
* This is useful in finding diffrences between the expected and the actual
|
||||
* output strings of a testcase.
|
||||
* @param {string} string whose whitespace needs to be made visible.
|
||||
* @return {string} string whose whitespace is made visible.
|
||||
*/
|
||||
goog.debug.makeWhitespaceVisible = function(string) {
|
||||
return string.replace(/ /g, '[_]')
|
||||
.replace(/\f/g, '[f]')
|
||||
.replace(/\n/g, '[n]\n')
|
||||
.replace(/\r/g, '[r]')
|
||||
.replace(/\t/g, '[t]');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hash map for storing function names that have already been looked up.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.fnNameCache_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Resolves functions to their names. Resolved function names will be cached.
|
||||
* @type {function(Function):string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.fnNameResolver_;
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debugTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debugTest');
|
||||
goog.setTestOnly('goog.debugTest');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testExposeException() {
|
||||
var expected =
|
||||
'Message: message"<br>' +
|
||||
'Url: <a href="view-source:http://fileName%22" ' +
|
||||
'target="_new">http://fileName"</a><br>' +
|
||||
'Line: lineNumber"<br><br>' +
|
||||
'Browser stack:<br>' +
|
||||
'stack"-> [end]';
|
||||
var error = {
|
||||
message: 'message"',
|
||||
fileName: 'http://fileName"',
|
||||
lineNumber: 'lineNumber"',
|
||||
stack: 'stack"'
|
||||
};
|
||||
var actualHtml = goog.debug.exposeExceptionAsHtml(error);
|
||||
var actual = goog.html.SafeHtml.unwrap(actualHtml);
|
||||
actual = actual.substring(0, expected.length);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
function testMakeWhitespaceVisible() {
|
||||
assertEquals(
|
||||
'Hello[_][_]World![r][n]\n' +
|
||||
'[r][n]\n' +
|
||||
'[f][f]I[_]am[t][t]here![r][n]\n',
|
||||
goog.debug.makeWhitespaceVisible(
|
||||
'Hello World!\r\n\r\n\f\fI am\t\there!\r\n'));
|
||||
}
|
||||
|
||||
function testGetFunctionName() {
|
||||
// Trivial resolver that matches just a few names: a static function, a
|
||||
// constructor, and a member function.
|
||||
var resolver = function(f) {
|
||||
if (f === goog.debug.getFunctionName) {
|
||||
return 'goog.debug.getFunctionName';
|
||||
} else if (f === goog.structs.Set) {
|
||||
return 'goog.structs.Set';
|
||||
} else if (f === goog.structs.Set.prototype.getCount) {
|
||||
return 'goog.structs.Set.getCount';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
goog.debug.setFunctionResolver(resolver);
|
||||
|
||||
assertEquals(
|
||||
'goog.debug.getFunctionName',
|
||||
goog.debug.getFunctionName(goog.debug.getFunctionName));
|
||||
assertEquals(
|
||||
'goog.structs.Set',
|
||||
goog.debug.getFunctionName(goog.structs.Set));
|
||||
var set = new goog.structs.Set();
|
||||
assertEquals(
|
||||
'goog.structs.Set.getCount',
|
||||
goog.debug.getFunctionName(set.getCount));
|
||||
|
||||
// This function is matched by the fallback heuristic.
|
||||
assertEquals(
|
||||
'testGetFunctionName',
|
||||
goog.debug.getFunctionName(testGetFunctionName));
|
||||
|
||||
goog.debug.setFunctionResolver(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that a substring can be found in a specified text string.
|
||||
*
|
||||
* @param {string} substring The substring to search for.
|
||||
* @param {string} text The text string to search within.
|
||||
*/
|
||||
function assertContainsSubstring(substring, text) {
|
||||
assertNotEquals('Could not find "' + substring + '" in "' + text + '"',
|
||||
-1, text.search(substring));
|
||||
}
|
||||
|
||||
|
||||
function testDeepExpose() {
|
||||
var a = {};
|
||||
var b = {};
|
||||
a.ancestor = a;
|
||||
a.otherObject = b;
|
||||
a.otherObjectAgain = b;
|
||||
|
||||
var deepExpose = goog.debug.deepExpose(a);
|
||||
|
||||
assertContainsSubstring('ancestor = ... reference loop detected ...',
|
||||
deepExpose);
|
||||
|
||||
assertContainsSubstring('otherObjectAgain = {', deepExpose);
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the DebugWindow class. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.DebugWindow');
|
||||
|
||||
goog.require('goog.debug.HtmlFormatter');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.structs.CircularBuffer');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a debug DebugWindow that is bound to the goog.debug.Logger.
|
||||
* It handles log messages and writes them to the DebugWindow. This doesn't
|
||||
* provide a lot of functionality that the old Gmail logging infrastructure
|
||||
* provided like saving debug logs for exporting to the server. Now that we
|
||||
* have an event-based logging infrastructure, we can encapsulate that
|
||||
* functionality in a separate class.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string=} opt_identifier Identifier for this logging class.
|
||||
* @param {string=} opt_prefix Prefix prepended to messages.
|
||||
*/
|
||||
goog.debug.DebugWindow = function(opt_identifier, opt_prefix) {
|
||||
/**
|
||||
* Identifier for this logging class
|
||||
* @protected {string}
|
||||
*/
|
||||
this.identifier = opt_identifier || '';
|
||||
|
||||
/**
|
||||
* Array used to buffer log output
|
||||
* @protected {!Array<string>}
|
||||
*/
|
||||
this.outputBuffer = [];
|
||||
|
||||
/**
|
||||
* Optional prefix to be prepended to error strings
|
||||
* @private {string}
|
||||
*/
|
||||
this.prefix_ = opt_prefix || '';
|
||||
|
||||
/**
|
||||
* Buffer for saving the last 1000 messages
|
||||
* @private {!goog.structs.CircularBuffer}
|
||||
*/
|
||||
this.savedMessages_ =
|
||||
new goog.structs.CircularBuffer(goog.debug.DebugWindow.MAX_SAVED);
|
||||
|
||||
/**
|
||||
* Save the publish handler so it can be removed
|
||||
* @private {!Function}
|
||||
*/
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
|
||||
/**
|
||||
* Formatter for formatted output
|
||||
* @private {goog.debug.Formatter}
|
||||
*/
|
||||
this.formatter_ = new goog.debug.HtmlFormatter(this.prefix_);
|
||||
|
||||
/**
|
||||
* Loggers that we shouldn't output
|
||||
* @private {!Object}
|
||||
*/
|
||||
this.filteredLoggers_ = {};
|
||||
|
||||
// enable by default
|
||||
this.setCapturing(true);
|
||||
|
||||
/**
|
||||
* Whether we are currently enabled. When the DebugWindow is enabled, it tries
|
||||
* to keep its window open. When it's disabled, it can still be capturing log
|
||||
* output if, but it won't try to write them to the DebugWindow window until
|
||||
* it's enabled.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.enabled_ = goog.debug.DebugWindow.isEnabled(this.identifier);
|
||||
|
||||
// timer to save the DebugWindow's window position in a cookie
|
||||
goog.global.setInterval(goog.bind(this.saveWindowPositionSize_, this), 7500);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Max number of messages to be saved
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.DebugWindow.MAX_SAVED = 500;
|
||||
|
||||
|
||||
/**
|
||||
* How long to keep the cookies for in milliseconds
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.DebugWindow.COOKIE_TIME = 30 * 24 * 60 * 60 * 1000; // 30-days
|
||||
|
||||
|
||||
/**
|
||||
* HTML string printed when the debug window opens
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.welcomeMessage = 'LOGGING';
|
||||
|
||||
|
||||
/**
|
||||
* Whether to force enable the window on a severe log.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.enableOnSevere_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to debug window
|
||||
* @type {Window}
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.win = null;
|
||||
|
||||
|
||||
/**
|
||||
* In the process of opening the window
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.winOpening_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we are currently capturing logger output.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isCapturing_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we already showed an alert that the DebugWindow was blocked.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.showedBlockedAlert_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to timeout used to buffer the output stream.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.bufferTimeout_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Timestamp for the last time the log was written to.
|
||||
* @protected {number}
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.lastCall = goog.now();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the welcome message shown when the window is first opened or reset.
|
||||
*
|
||||
* @param {string} msg An HTML string.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setWelcomeMessage = function(msg) {
|
||||
this.welcomeMessage = msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the debug window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.init = function() {
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the DebugWindow is enabled. When the DebugWindow is enabled, it
|
||||
* tries to keep its window open and logs all messages to the window. When the
|
||||
* DebugWindow is disabled, it stops logging messages to its window.
|
||||
*
|
||||
* @return {boolean} Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isEnabled = function() {
|
||||
return this.enabled_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the DebugWindow is enabled. When the DebugWindow is enabled, it
|
||||
* tries to keep its window open and log all messages to the window. When the
|
||||
* DebugWindow is disabled, it stops logging messages to its window. The
|
||||
* DebugWindow also saves this state to a cookie so that it's persisted across
|
||||
* application refreshes.
|
||||
* @param {boolean} enable Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setEnabled = function(enable) {
|
||||
this.enabled_ = enable;
|
||||
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
}
|
||||
|
||||
this.setCookie_('enabled', enable ? '1' : '0');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the debug window should be force enabled when a severe log is
|
||||
* encountered.
|
||||
* @param {boolean} enableOnSevere Whether to enable on severe logs..
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setForceEnableOnSevere =
|
||||
function(enableOnSevere) {
|
||||
this.enableOnSevere_ = enableOnSevere;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether we are currently capturing logger output.
|
||||
* @return {boolean} whether we are currently capturing logger output.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isCapturing = function() {
|
||||
return this.isCapturing_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the formatter for outputting to the debug window. The default formatter
|
||||
* is an instance of goog.debug.HtmlFormatter
|
||||
* @return {goog.debug.Formatter} The formatter in use.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the formatter for outputting to the debug window.
|
||||
* @param {goog.debug.Formatter} formatter The formatter to use.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setFormatter = function(formatter) {
|
||||
this.formatter_ = formatter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a separator to the debug window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addSeparator = function() {
|
||||
this.write_('<hr>');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether there is an active window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.hasActiveWindow = function() {
|
||||
return !!this.win && !this.win.closed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the contents of the debug window
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.clear = function() {
|
||||
this.savedMessages_.clear();
|
||||
if (this.hasActiveWindow()) {
|
||||
this.writeInitialDocument();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord the LogRecord.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addLogRecord = function(logRecord) {
|
||||
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
|
||||
return;
|
||||
}
|
||||
var html = this.formatter_.formatRecord(logRecord);
|
||||
this.write_(html);
|
||||
if (this.enableOnSevere_ &&
|
||||
logRecord.getLevel().value >= goog.debug.Logger.Level.SEVERE.value) {
|
||||
this.setEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes a message to the log, possibly opening up the window if it's enabled,
|
||||
* or saving it if it's disabled.
|
||||
* @param {string} html The HTML to write.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.write_ = function(html) {
|
||||
// If the logger is enabled, open window and write html message to log
|
||||
// otherwise save it
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
this.savedMessages_.add(html);
|
||||
this.writeToLog_(html);
|
||||
} else {
|
||||
this.savedMessages_.add(html);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write to the buffer. If a message hasn't been sent for more than 750ms just
|
||||
* write, otherwise delay for a minimum of 250ms.
|
||||
* @param {string} html HTML to post to the log.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeToLog_ = function(html) {
|
||||
this.outputBuffer.push(html);
|
||||
goog.global.clearTimeout(this.bufferTimeout_);
|
||||
|
||||
if (goog.now() - this.lastCall > 750) {
|
||||
this.writeBufferToLog();
|
||||
} else {
|
||||
this.bufferTimeout_ =
|
||||
goog.global.setTimeout(goog.bind(this.writeBufferToLog, this), 250);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write to the log and maybe scroll into view.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeBufferToLog = function() {
|
||||
this.lastCall = goog.now();
|
||||
if (this.hasActiveWindow()) {
|
||||
var body = this.win.document.body;
|
||||
var scroll = body &&
|
||||
body.scrollHeight - (body.scrollTop + body.clientHeight) <= 100;
|
||||
|
||||
this.win.document.write(this.outputBuffer.join(''));
|
||||
this.outputBuffer.length = 0;
|
||||
|
||||
if (scroll) {
|
||||
this.win.scrollTo(0, 1000000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes all saved messages to the DebugWindow.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeSavedMessages = function() {
|
||||
var messages = this.savedMessages_.getValues();
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
this.writeToLog_(messages[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens the debug window if it is not already referenced
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.openWindow_ = function() {
|
||||
if (this.hasActiveWindow() || this.winOpening_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var winpos = this.getCookie_('dbg', '0,0,800,500').split(',');
|
||||
var x = Number(winpos[0]);
|
||||
var y = Number(winpos[1]);
|
||||
var w = Number(winpos[2]);
|
||||
var h = Number(winpos[3]);
|
||||
|
||||
this.winOpening_ = true;
|
||||
this.win = window.open('', this.getWindowName_(), 'width=' + w +
|
||||
',height=' + h + ',toolbar=no,resizable=yes,' +
|
||||
'scrollbars=yes,left=' + x + ',top=' + y +
|
||||
',status=no,screenx=' + x + ',screeny=' + y);
|
||||
|
||||
if (!this.win) {
|
||||
if (!this.showedBlockedAlert_) {
|
||||
// only show this once
|
||||
alert('Logger popup was blocked');
|
||||
this.showedBlockedAlert_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.winOpening_ = false;
|
||||
|
||||
if (this.win) {
|
||||
this.writeInitialDocument();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a valid window name for the debug window. Replaces invalid characters in
|
||||
* IE.
|
||||
* @return {string} Valid window name.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getWindowName_ = function() {
|
||||
return goog.userAgent.IE ?
|
||||
this.identifier.replace(/[\s\-\.\,]/g, '_') : this.identifier;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The style rule text, for inclusion in the initial HTML.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getStyleRules = function() {
|
||||
return '*{font:normal 14px monospace;}' +
|
||||
'.dbg-sev{color:#F00}' +
|
||||
'.dbg-w{color:#E92}' +
|
||||
'.dbg-sh{background-color:#fd4;font-weight:bold;color:#000}' +
|
||||
'.dbg-i{color:#666}' +
|
||||
'.dbg-f{color:#999}' +
|
||||
'.dbg-ev{color:#0A0}' +
|
||||
'.dbg-m{color:#990}';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes the initial HTML of the debug window.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeInitialDocument = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.win.document.open();
|
||||
|
||||
var html = '<style>' + this.getStyleRules() + '</style>' +
|
||||
'<hr><div class="dbg-ev" style="text-align:center">' +
|
||||
this.welcomeMessage + '<br><small>Logger: ' +
|
||||
this.identifier + '</small></div><hr>';
|
||||
|
||||
this.writeToLog_(html);
|
||||
this.writeSavedMessages();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save persistent data (using cookies) for 1 month (cookie specific to this
|
||||
* logger object).
|
||||
* @param {string} key Data name.
|
||||
* @param {string} value Data value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setCookie_ = function(key, value) {
|
||||
var fullKey = goog.debug.DebugWindow.getCookieKey_(this.identifier, key);
|
||||
document.cookie = fullKey + '=' + encodeURIComponent(value) +
|
||||
';path=/;expires=' +
|
||||
(new Date(goog.now() + goog.debug.DebugWindow.COOKIE_TIME)).toUTCString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve data (using cookies).
|
||||
* @param {string} key Data name.
|
||||
* @param {string=} opt_default Optional default value if cookie doesn't exist.
|
||||
* @return {string} Cookie value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getCookie_ = function(key, opt_default) {
|
||||
return goog.debug.DebugWindow.getCookieValue_(
|
||||
this.identifier, key, opt_default);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a valid cookie key name which is scoped to the given identifier.
|
||||
* Substitutes all occurences of invalid cookie name characters (whitespace,
|
||||
* ';', and '=') with '_', which is a valid and readable alternative.
|
||||
* @see goog.net.Cookies#isValidName
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
|
||||
* @param {string} identifier Identifier for logging class.
|
||||
* @param {string} key Data name.
|
||||
* @return {string} Cookie key name.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.getCookieKey_ = function(identifier, key) {
|
||||
var fullKey = key + identifier;
|
||||
return fullKey.replace(/[;=\s]/g, '_');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve data (using cookies).
|
||||
* @param {string} identifier Identifier for logging class.
|
||||
* @param {string} key Data name.
|
||||
* @param {string=} opt_default Optional default value if cookie doesn't exist.
|
||||
* @return {string} Cookie value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.getCookieValue_ = function(
|
||||
identifier, key, opt_default) {
|
||||
var fullKey = goog.debug.DebugWindow.getCookieKey_(identifier, key);
|
||||
var cookie = String(document.cookie);
|
||||
var start = cookie.indexOf(fullKey + '=');
|
||||
if (start != -1) {
|
||||
var end = cookie.indexOf(';', start);
|
||||
return decodeURIComponent(cookie.substring(start + fullKey.length + 1,
|
||||
end == -1 ? cookie.length : end));
|
||||
} else {
|
||||
return opt_default || '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} identifier Identifier for logging class.
|
||||
* @return {boolean} Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.isEnabled = function(identifier) {
|
||||
return goog.debug.DebugWindow.getCookieValue_(identifier, 'enabled') == '1';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saves the window position size to a cookie
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.saveWindowPositionSize_ = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
var x = this.win.screenX || this.win.screenLeft || 0;
|
||||
var y = this.win.screenY || this.win.screenTop || 0;
|
||||
var w = this.win.outerWidth || 800;
|
||||
var h = this.win.outerHeight || 500;
|
||||
this.setCookie_('dbg', x + ',' + y + ',' + w + ',' + h);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to add.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addFilter = function(loggerName) {
|
||||
this.filteredLoggers_[loggerName] = 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to remove.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.removeFilter = function(loggerName) {
|
||||
delete this.filteredLoggers_[loggerName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Modify the size of the circular buffer. Allows the log to retain more
|
||||
* information while the window is closed.
|
||||
* @param {number} size New size of the circular buffer.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.resetBufferWithNewSize = function(size) {
|
||||
if (size > 0 && size < 50000) {
|
||||
this.clear();
|
||||
this.savedMessages_ = new goog.structs.CircularBuffer(size);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.DebugWindow
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.DebugWindowTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2012 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.DebugWindowTest');
|
||||
goog.setTestOnly('goog.debug.DebugWindowTest');
|
||||
|
||||
goog.require('goog.debug.DebugWindow');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testGetCookieKey() {
|
||||
assertEquals(
|
||||
'keyNasty_Debug__Identifier_',
|
||||
goog.debug.DebugWindow.getCookieKey_('Nasty_Debug =Identifier;', 'key'));
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Runtime development CSS Compiler emulation, via javascript.
|
||||
* This class provides an approximation to CSSCompiler's functionality by
|
||||
* hacking the live CSSOM.
|
||||
* This code is designed to be inserted in the DOM immediately after the last
|
||||
* style block in HEAD when in development mode, i.e. you are not using a
|
||||
* running instance of a CSS Compiler to pass your CSS through.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.debug.DevCss');
|
||||
goog.provide('goog.debug.DevCss.UserAgent');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.cssom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for solving development CSS issues/emulating the CSS Compiler.
|
||||
* @param {goog.debug.DevCss.UserAgent=} opt_userAgent The user agent, if not
|
||||
* passed in, will be determined using goog.userAgent.
|
||||
* @param {number|string=} opt_userAgentVersion The user agent's version.
|
||||
* If not passed in, will be determined using goog.userAgent.
|
||||
* @throws {Error} When userAgent detection fails.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.debug.DevCss = function(opt_userAgent, opt_userAgentVersion) {
|
||||
if (!opt_userAgent) {
|
||||
// Walks through the known goog.userAgents.
|
||||
if (goog.userAgent.IE) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.IE;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.GECKO;
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.WEBKIT;
|
||||
} else if (goog.userAgent.MOBILE) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.MOBILE;
|
||||
} else if (goog.userAgent.OPERA) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.OPERA;
|
||||
}
|
||||
}
|
||||
switch (opt_userAgent) {
|
||||
case goog.debug.DevCss.UserAgent.OPERA:
|
||||
case goog.debug.DevCss.UserAgent.IE:
|
||||
case goog.debug.DevCss.UserAgent.GECKO:
|
||||
case goog.debug.DevCss.UserAgent.FIREFOX:
|
||||
case goog.debug.DevCss.UserAgent.WEBKIT:
|
||||
case goog.debug.DevCss.UserAgent.SAFARI:
|
||||
case goog.debug.DevCss.UserAgent.MOBILE:
|
||||
break;
|
||||
default:
|
||||
throw Error('Could not determine the user agent from known UserAgents');
|
||||
}
|
||||
|
||||
/**
|
||||
* One of goog.debug.DevCss.UserAgent.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.userAgent_ = opt_userAgent;
|
||||
|
||||
/**
|
||||
* @const @private
|
||||
*/
|
||||
this.userAgentTokens_ = {};
|
||||
|
||||
/**
|
||||
* @type {number|string}
|
||||
* @private
|
||||
*/
|
||||
this.userAgentVersion_ = opt_userAgentVersion || goog.userAgent.VERSION;
|
||||
this.generateUserAgentTokens_();
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isIe6OrLess_ = this.userAgent_ == goog.debug.DevCss.UserAgent.IE &&
|
||||
goog.string.compareVersions('7', this.userAgentVersion_) > 0;
|
||||
|
||||
if (this.isIe6OrLess_) {
|
||||
/**
|
||||
* @type {Array<{classNames,combinedClassName,els}>}
|
||||
* @private
|
||||
*/
|
||||
this.ie6CombinedMatches_ = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rewrites the CSSOM as needed to activate any useragent-specific selectors.
|
||||
* @param {boolean=} opt_enableIe6ReadyHandler If true(the default), and the
|
||||
* userAgent is ie6, we set a document "ready" event handler to walk the DOM
|
||||
* and make combined selector className changes. Having this parameter also
|
||||
* aids unit testing.
|
||||
*/
|
||||
goog.debug.DevCss.prototype.activateBrowserSpecificCssRules = function(
|
||||
opt_enableIe6ReadyHandler) {
|
||||
var enableIe6EventHandler = goog.isDef(opt_enableIe6ReadyHandler) ?
|
||||
opt_enableIe6ReadyHandler : true;
|
||||
var cssRules = goog.cssom.getAllCssStyleRules();
|
||||
|
||||
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
|
||||
this.replaceBrowserSpecificClassNames_(cssRule);
|
||||
}
|
||||
|
||||
// Since we may have manipulated the rules above, we'll have to do a
|
||||
// complete sweep again if we're in IE6. Luckily performance doesn't
|
||||
// matter for this tool.
|
||||
if (this.isIe6OrLess_) {
|
||||
cssRules = goog.cssom.getAllCssStyleRules();
|
||||
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
|
||||
this.replaceIe6CombinedSelectors_(cssRule);
|
||||
}
|
||||
}
|
||||
|
||||
// Add an event listener for document ready to rewrite any necessary
|
||||
// combined classnames in IE6.
|
||||
if (this.isIe6OrLess_ && enableIe6EventHandler) {
|
||||
goog.events.listen(document, goog.events.EventType.LOAD, goog.bind(
|
||||
this.addIe6CombinedClassNames_, this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A list of possible user agent strings.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.debug.DevCss.UserAgent = {
|
||||
OPERA: 'OPERA',
|
||||
IE: 'IE',
|
||||
GECKO: 'GECKO',
|
||||
FIREFOX: 'GECKO',
|
||||
WEBKIT: 'WEBKIT',
|
||||
SAFARI: 'WEBKIT',
|
||||
MOBILE: 'MOBILE'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A list of strings that may be used for matching in CSS files/development.
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.CssToken_ = {
|
||||
USERAGENT: 'USERAGENT',
|
||||
SEPARATOR: '-',
|
||||
LESS_THAN: 'LT',
|
||||
GREATER_THAN: 'GT',
|
||||
LESS_THAN_OR_EQUAL: 'LTE',
|
||||
GREATER_THAN_OR_EQUAL: 'GTE',
|
||||
IE6_SELECTOR_TEXT: 'goog-ie6-selector',
|
||||
IE6_COMBINED_GLUE: '_'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generates user agent token match strings with comparison and version bits.
|
||||
* For example:
|
||||
* userAgentTokens_.ANY will be like 'GECKO'
|
||||
* userAgentTokens_.LESS_THAN will be like 'GECKO-LT3' etc...
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.generateUserAgentTokens_ = function() {
|
||||
this.userAgentTokens_.ANY = goog.debug.DevCss.CssToken_.USERAGENT +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR + this.userAgent_;
|
||||
this.userAgentTokens_.EQUALS = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR;
|
||||
this.userAgentTokens_.LESS_THAN = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.LESS_THAN;
|
||||
this.userAgentTokens_.LESS_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.LESS_THAN_OR_EQUAL;
|
||||
this.userAgentTokens_.GREATER_THAN = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.GREATER_THAN;
|
||||
this.userAgentTokens_.GREATER_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.GREATER_THAN_OR_EQUAL;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the version number bit from a selector matching userAgentToken.
|
||||
* @param {string} selectorText The selector text of a CSS rule.
|
||||
* @param {string} userAgentToken Includes the LTE/GTE bit to see if it matches.
|
||||
* @return {string|undefined} The version number.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getVersionNumberFromSelectorText_ = function(
|
||||
selectorText, userAgentToken) {
|
||||
var regex = new RegExp(userAgentToken + '([\\d\\.]+)');
|
||||
var matches = regex.exec(selectorText);
|
||||
if (matches && matches.length == 2) {
|
||||
return matches[1];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extracts a rule version from the selector text, and if it finds one, calls
|
||||
* compareVersions against it and the passed in token string to provide the
|
||||
* value needed to determine if we have a match or not.
|
||||
* @param {CSSRule} cssRule The rule to test against.
|
||||
* @param {string} token The match token to test against the rule.
|
||||
* @return {!Array|undefined} A tuple with the result of the compareVersions
|
||||
* call and the matched ruleVersion.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getRuleVersionAndCompare_ = function(cssRule,
|
||||
token) {
|
||||
if (!cssRule.selectorText.match(token)) {
|
||||
return;
|
||||
}
|
||||
var ruleVersion = this.getVersionNumberFromSelectorText_(
|
||||
cssRule.selectorText, token);
|
||||
if (!ruleVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
var comparison = goog.string.compareVersions(this.userAgentVersion_,
|
||||
ruleVersion);
|
||||
return [comparison, ruleVersion];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces a CSS selector if we have matches based on our useragent/version.
|
||||
* Example: With a selector like ".USERAGENT-IE-LTE6 .class { prop: value }" if
|
||||
* we are running IE6 we'll end up with ".class { prop: value }", thereby
|
||||
* "activating" the selector.
|
||||
* @param {CSSRule} cssRule The cssRule to potentially replace.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.replaceBrowserSpecificClassNames_ = function(
|
||||
cssRule) {
|
||||
|
||||
// If we don't match the browser token, we can stop now.
|
||||
if (!cssRule.selectorText.match(this.userAgentTokens_.ANY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We know it will begin as a classname.
|
||||
var additionalRegexString;
|
||||
|
||||
// Tests "Less than or equals".
|
||||
var compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.LESS_THAN_OR_EQUAL);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] > 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.LESS_THAN_OR_EQUAL +
|
||||
compared[1];
|
||||
}
|
||||
|
||||
// Tests "Less than".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.LESS_THAN);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] > -1) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.LESS_THAN + compared[1];
|
||||
}
|
||||
|
||||
// Tests "Greater than or equals".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.GREATER_THAN_OR_EQUAL);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] < 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.GREATER_THAN_OR_EQUAL +
|
||||
compared[1];
|
||||
}
|
||||
|
||||
// Tests "Greater than".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.GREATER_THAN);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] < 1) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.GREATER_THAN + compared[1];
|
||||
}
|
||||
|
||||
// Tests "Equals".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.EQUALS);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] != 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.EQUALS + compared[1];
|
||||
}
|
||||
|
||||
// If we got to here without generating the additionalRegexString, then
|
||||
// we did not match any of our comparison token strings, and we want a
|
||||
// general browser token replacement.
|
||||
if (!additionalRegexString) {
|
||||
additionalRegexString = this.userAgentTokens_.ANY;
|
||||
}
|
||||
|
||||
// We need to match at least a single whitespace character to know that
|
||||
// we are matching the entire useragent string token.
|
||||
var regexString = '\\.' + additionalRegexString + '\\s+';
|
||||
var re = new RegExp(regexString, 'g');
|
||||
|
||||
var currentCssText = goog.cssom.getCssTextFromCssRule(cssRule);
|
||||
|
||||
// Replacing the token with '' activates the selector for this useragent.
|
||||
var newCssText = currentCssText.replace(re, '');
|
||||
|
||||
if (newCssText != currentCssText) {
|
||||
goog.cssom.replaceCssRule(cssRule, newCssText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces IE6 combined selector rules with a workable development alternative.
|
||||
* IE6 actually parses .class1.class2 {} to simply .class2 {} which is nasty.
|
||||
* To fully support combined selectors in IE6 this function needs to be paired
|
||||
* with a call to replace the relevant DOM elements classNames as well.
|
||||
* @see {this.addIe6CombinedClassNames_}
|
||||
* @param {CSSRule} cssRule The rule to potentially fix.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.replaceIe6CombinedSelectors_ = function(cssRule) {
|
||||
// This match only ever works in IE because other UA's won't have our
|
||||
// IE6_SELECTOR_TEXT in the cssText property.
|
||||
if (cssRule.style.cssText &&
|
||||
cssRule.style.cssText.match(
|
||||
goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT)) {
|
||||
var cssText = goog.cssom.getCssTextFromCssRule(cssRule);
|
||||
var combinedSelectorText = this.getIe6CombinedSelectorText_(cssText);
|
||||
if (combinedSelectorText) {
|
||||
var newCssText = combinedSelectorText + '{' + cssRule.style.cssText + '}';
|
||||
goog.cssom.replaceCssRule(cssRule, newCssText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the appropriate new combined selector text for IE6.
|
||||
* Also adds an entry onto ie6CombinedMatches_ with relevant info for the
|
||||
* likely following call to walk the DOM and rewrite the class attribute.
|
||||
* Example: With a selector like
|
||||
* ".class2 { -goog-ie6-selector: .class1.class2; prop: value }".
|
||||
* this function will return:
|
||||
* ".class1_class2 { prop: value }".
|
||||
* @param {string} cssText The CSS selector text and css rule text combined.
|
||||
* @return {?string} The rewritten css rule text.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getIe6CombinedSelectorText_ = function(cssText) {
|
||||
var regex = new RegExp(goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT +
|
||||
'\\s*:\\s*\\"([^\\"]+)\\"', 'gi');
|
||||
var matches = regex.exec(cssText);
|
||||
if (matches) {
|
||||
var combinedSelectorText = matches[1];
|
||||
// To aid in later fixing the DOM, we need to split up the possible
|
||||
// selector groups by commas.
|
||||
var groupedSelectors = combinedSelectorText.split(/\s*\,\s*/);
|
||||
for (var i = 0, selector; selector = groupedSelectors[i]; i++) {
|
||||
// Strips off the leading ".".
|
||||
var combinedClassName = selector.substr(1);
|
||||
var classNames = combinedClassName.split(
|
||||
goog.debug.DevCss.CssToken_.IE6_COMBINED_GLUE);
|
||||
var entry = {
|
||||
classNames: classNames,
|
||||
combinedClassName: combinedClassName,
|
||||
els: []
|
||||
};
|
||||
this.ie6CombinedMatches_.push(entry);
|
||||
}
|
||||
return combinedSelectorText;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds combined selectors with underscores to make them "work" in IE6.
|
||||
* @see {this.replaceIe6CombinedSelectors_}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.addIe6CombinedClassNames_ = function() {
|
||||
if (!this.ie6CombinedMatches_.length) {
|
||||
return;
|
||||
}
|
||||
var allEls = document.getElementsByTagName('*');
|
||||
var matches = [];
|
||||
// Match nodes for all classNames.
|
||||
for (var i = 0, classNameEntry; classNameEntry =
|
||||
this.ie6CombinedMatches_[i]; i++) {
|
||||
for (var j = 0, el; el = allEls[j]; j++) {
|
||||
var classNamesLength = classNameEntry.classNames.length;
|
||||
for (var k = 0, className; className = classNameEntry.classNames[k];
|
||||
k++) {
|
||||
if (!goog.dom.classlist.contains(goog.asserts.assert(el), className)) {
|
||||
break;
|
||||
}
|
||||
if (k == classNamesLength - 1) {
|
||||
classNameEntry.els.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Walks over our matching nodes and fixes them.
|
||||
if (classNameEntry.els.length) {
|
||||
for (var j = 0, el; el = classNameEntry.els[j]; j++) {
|
||||
goog.asserts.assert(el);
|
||||
if (!goog.dom.classlist.contains(el,
|
||||
classNameEntry.combinedClassName)) {
|
||||
goog.dom.classlist.add(el, classNameEntry.combinedClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - CSS Object Model helper
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.DevCssTest');
|
||||
</script>
|
||||
<style>
|
||||
@import "devcss_test_import_1.css";
|
||||
|
||||
.css-style-1 {
|
||||
background-color: rgb(173,216,230); /* lightblue */
|
||||
padding: 10px;
|
||||
}
|
||||
.css-style-2 {
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
padding: 10px;
|
||||
}
|
||||
.css-style-3 {
|
||||
background-color: orange;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.compound1.compound2 {
|
||||
-goog-ie6-selector: ".compound1_compound2";
|
||||
background-color: rgb(255,192,203); /* aka pink */
|
||||
}
|
||||
|
||||
.USERAGENT-OPERA .css-style-2,
|
||||
.USERAGENT-GECKO .css-style-2,
|
||||
.USERAGENT-MOBILE .css-style-2 {
|
||||
background-color: rgb(255,192,203); /* aka pink */
|
||||
}
|
||||
|
||||
.USERAGENT-OPERA .css-style-2,
|
||||
.USERAGENT-WEBKIT-GTE255 .css-style-2,
|
||||
.USERAGENT-MOBILE .css-style-2 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.USERAGENT-OPERA .css-style-2,
|
||||
.USERAGENT-WEBKIT-LTE200 .css-style-2,
|
||||
.USERAGENT-MOBILE .css-style-2 {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.USERAGENT-GECKO #devcss-gecko-1,
|
||||
.USERAGENT-GECKO #devcss-gecko-2 {
|
||||
background-color: rgb(255,192,203); /* aka pink */
|
||||
}
|
||||
.USERAGENT-IE-8 #devcss-ie8-1,
|
||||
.USERAGENT-IE-8 #devcss-ie8-2 {
|
||||
background-color: rgb(255,192,203); /* aka pink */
|
||||
}
|
||||
#devcss-test-ie6 {
|
||||
background-color: purple;
|
||||
}
|
||||
.USERAGENT-IE-6 #devcss-test-ie6 {
|
||||
background-color: rgb(255,192,203); /* aka pink */
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
@import "devcss_test_import_2.css";
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="devcss-test-2" class="css-style-2">
|
||||
<ul>
|
||||
<li>
|
||||
Should end up background-color:rgb(255, 192, 203) aka pink.
|
||||
</li>
|
||||
<li>
|
||||
Should end up with margin-top: 20px; margin-left: 15px;
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div id="devcss-test-ie6">
|
||||
<ul>
|
||||
<li>
|
||||
Should end up background-color:rgb(255, 192, 203) aka pink, not black or purple.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!--
|
||||
TODO(user): Re-enable if we ever come up with a way to make imports
|
||||
work.
|
||||
<div id="devcss-test-importfixer-1" class="css-style-3">
|
||||
<ul>
|
||||
<li>
|
||||
Should end up yellow from the first import of
|
||||
devcss_test_import_2.css, overriding the previous orange setting and
|
||||
ignoring the second import of devcss_test_import_1.css which would set
|
||||
it grey.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="devcss-test-importfixer-2" class="css-style-1">
|
||||
<ul>
|
||||
<li>
|
||||
Should end up lightblue, which is a revert back to the setting in this
|
||||
test file, after disabling the second import of
|
||||
devcss_test_import_1.css
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
-->
|
||||
<br>
|
||||
<br>
|
||||
<div id="devcss-ie8-1">
|
||||
This element should be pink for IE8.
|
||||
</div>
|
||||
<div id="devcss-ie8-2">
|
||||
This element should also be pink for IE8.
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div id="devcss-gecko-1">
|
||||
This element should be pink for Gecko.
|
||||
</div>
|
||||
<div id="devcss-gecko-2">
|
||||
This element should also be pink for Gecko.
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div id="devcss-test-compound1" class="compound1">
|
||||
This (class="compound1") should have no bg.
|
||||
</div>
|
||||
<div id="devcss-test-compound2" class="compound2">
|
||||
This (class="compound2") should have no bg.
|
||||
</div>
|
||||
<div id="devcss-test-compound1-2" class="compound1 compound2 compound1_compound2">
|
||||
This (class="compound1 compound2 compound1_compound2") should have
|
||||
a pink bg for IE6.
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<input id="devcss-test-combined1" type="hidden" class="ie6-1 ie6-2">
|
||||
<input id="devcss-test-combined2" type="hidden" class="ie6-3 ie6-1 ie6-2">
|
||||
<input id="devcss-test-notcombined1" type="hidden" class="ie6-1">
|
||||
<input id="devcss-test-notcombined2" type="hidden" class="ie6-2">
|
||||
<input id="devcss-test-notcombined3" type="hidden" class="ie6-3">
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.DevCssTest');
|
||||
goog.setTestOnly('goog.debug.DevCssTest');
|
||||
|
||||
goog.require('goog.debug.DevCss');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var el;
|
||||
|
||||
function setUpPage() {
|
||||
el = document.getElementById('devcss-test-2');
|
||||
}
|
||||
|
||||
// Since background color sometimes comes back like rgb(xxx, xxx, xxx)
|
||||
// or rgb(xxx,xxx,xxx) depending on browser.
|
||||
function spaceless(foo) {
|
||||
return foo.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
function testGetIe6CombinedSelectorText() {
|
||||
var devcssInstance = new goog.debug.DevCss();
|
||||
devcssInstance.ie6CombinedMatches_ = [];
|
||||
var css = '.class2 { -goog-ie6-selector:".class1_class2"; prop: val; }';
|
||||
var newCss = devcssInstance.getIe6CombinedSelectorText_(css);
|
||||
assertEquals('.class1_class2', newCss);
|
||||
assertArrayEquals(['class1', 'class2'],
|
||||
devcssInstance.ie6CombinedMatches_[0].classNames);
|
||||
assertEquals('class1_class2',
|
||||
devcssInstance.ie6CombinedMatches_[0].combinedClassName);
|
||||
|
||||
devcssInstance = new goog.debug.DevCss();
|
||||
devcssInstance.ie6CombinedMatches_ = [];
|
||||
css = '.class3 { prop: val; -goog-ie6-selector:".class1_class2_class3";' +
|
||||
'prop: val; }';
|
||||
newCss = devcssInstance.getIe6CombinedSelectorText_(css);
|
||||
assertEquals('.class1_class2_class3', newCss);
|
||||
assertArrayEquals(['class1', 'class2', 'class3'],
|
||||
devcssInstance.ie6CombinedMatches_[0].classNames);
|
||||
assertEquals('class1_class2_class3',
|
||||
devcssInstance.ie6CombinedMatches_[0].combinedClassName);
|
||||
|
||||
devcssInstance = new goog.debug.DevCss();
|
||||
devcssInstance.ie6CombinedMatches_ = [];
|
||||
css = '.class3, .class5 {' +
|
||||
'-goog-ie6-selector:".class1_class2_class3, .class4_class5";' +
|
||||
'prop: val; }';
|
||||
newCss = devcssInstance.getIe6CombinedSelectorText_(css);
|
||||
assertEquals('.class1_class2_class3, .class4_class5', newCss);
|
||||
assertArrayEquals(['class1', 'class2', 'class3'],
|
||||
devcssInstance.ie6CombinedMatches_[0].classNames);
|
||||
assertEquals('class1_class2_class3',
|
||||
devcssInstance.ie6CombinedMatches_[0].combinedClassName);
|
||||
assertArrayEquals(['class4', 'class5'],
|
||||
devcssInstance.ie6CombinedMatches_[1].classNames);
|
||||
assertEquals('class4_class5',
|
||||
devcssInstance.ie6CombinedMatches_[1].combinedClassName);
|
||||
}
|
||||
|
||||
function testAddIe6CombinedClassNames() {
|
||||
var el_combined1 = document.getElementById('devcss-test-combined1');
|
||||
var el_combined2 = document.getElementById('devcss-test-combined2');
|
||||
var el_notcombined1 = document.getElementById('devcss-test-notcombined1');
|
||||
var el_notcombined2 = document.getElementById('devcss-test-notcombined2');
|
||||
var el_notcombined3 = document.getElementById('devcss-test-notcombined3');
|
||||
|
||||
var devcssInstance = new goog.debug.DevCss();
|
||||
devcssInstance.ie6CombinedMatches_ = [
|
||||
{
|
||||
classNames: ['ie6-2', 'ie6-1'],
|
||||
combinedClassName: 'ie6-1_ie6-2',
|
||||
els: []
|
||||
},
|
||||
{
|
||||
classNames: ['ie6-2', 'ie6-3', 'ie6-1'],
|
||||
combinedClassName: 'ie6-1_ie6-2_ie6-3',
|
||||
els: []
|
||||
}];
|
||||
|
||||
devcssInstance.addIe6CombinedClassNames_();
|
||||
assertEquals(-1, el_notcombined1.className.indexOf('ie6-1_ie6-2'));
|
||||
assertEquals(-1, el_notcombined2.className.indexOf('ie6-1_ie6-2'));
|
||||
assertEquals(-1, el_notcombined3.className.indexOf('ie6-1_ie6-2_ie6-3'));
|
||||
assertTrue(el_combined1.className.indexOf('ie6-1_ie6-2') != -1);
|
||||
assertTrue(el_combined2.className.indexOf('ie6-1_ie6-2_ie6-3') != -1);
|
||||
}
|
||||
|
||||
function testActivateBrowserSpecificCssALL() {
|
||||
// equals GECKO
|
||||
var devcssInstance = new goog.debug.DevCss('GECKO');
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var backgroundColor = goog.style.getBackgroundColor(el);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
|
||||
// GECKO test case w/ two selectors joined by a commma.
|
||||
var elGeckoOne = document.getElementById('devcss-gecko-1');
|
||||
backgroundColor = goog.style.getBackgroundColor(elGeckoOne);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
var elGeckoTwo = document.getElementById('devcss-gecko-2');
|
||||
backgroundColor = goog.style.getBackgroundColor(elGeckoTwo);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
}
|
||||
|
||||
|
||||
function testActivateBrowserSpecificCssWithVersion() {
|
||||
// equals IE 6
|
||||
var devcssInstance = new goog.debug.DevCss('IE', '6');
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var elIe6 = document.getElementById('devcss-test-ie6');
|
||||
var backgroundColor = goog.style.getBackgroundColor(elIe6);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
|
||||
// IE8 test case w/ two selectors joined by a commma.
|
||||
var devCssInstanceTwo = new goog.debug.DevCss('IE', '8');
|
||||
devCssInstanceTwo.activateBrowserSpecificCssRules(false);
|
||||
var elIe8One = document.getElementById('devcss-ie8-1');
|
||||
backgroundColor = goog.style.getBackgroundColor(elIe8One);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
var elIe8Two = document.getElementById('devcss-ie8-2');
|
||||
backgroundColor = goog.style.getBackgroundColor(elIe8Two);
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
}
|
||||
|
||||
function testActivateBrowserSpecificCssGteInvalid() {
|
||||
// WEBKIT_GTE255
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(1, marginBox.top); // should still be 1
|
||||
|
||||
var devcssInstance = new goog.debug.DevCss('WEBKIT', 254);
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(1, marginBox.top); // should still be 1
|
||||
}
|
||||
|
||||
function testActivateBrowserSpecificCssGteValid() {
|
||||
var devcssInstance = new goog.debug.DevCss('WEBKIT', 255);
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(20, marginBox.top);
|
||||
}
|
||||
|
||||
function testActivateBrowserSpecificCssLteInvalid() {
|
||||
// IE_LTE6
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(1, marginBox.left); // should still be 1
|
||||
|
||||
var devcssInstance = new goog.debug.DevCss('WEBKIT', 202);
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(1, marginBox.left); // should still be 1
|
||||
}
|
||||
|
||||
function testActivateBrowserSpecificCssLteValid() {
|
||||
var devcssInstance = new goog.debug.DevCss('WEBKIT', 199);
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
var marginBox = goog.style.getMarginBox(el);
|
||||
assertEquals(15, marginBox.left);
|
||||
}
|
||||
|
||||
function testReplaceIe6Selectors() {
|
||||
var devcssInstance = new goog.debug.DevCss('IE', 6);
|
||||
devcssInstance.activateBrowserSpecificCssRules(false);
|
||||
|
||||
// It should correctly be transparent, even in IE6.
|
||||
var compound2El = document.getElementById('devcss-test-compound2');
|
||||
var backgroundColor = spaceless(
|
||||
goog.style.getBackgroundColor(compound2El));
|
||||
|
||||
assertTrue('Unexpected background color: ' + backgroundColor,
|
||||
'transparent' == backgroundColor ||
|
||||
'rgba(0,0,0,0)' == backgroundColor);
|
||||
|
||||
// And this one should have the combined selector working, even in
|
||||
// IE6.
|
||||
backgroundColor = goog.style.getBackgroundColor(
|
||||
document.getElementById('devcss-test-compound1-2'));
|
||||
assertEquals('rgb(255,192,203)', spaceless(backgroundColor));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(user): Re-enable if we ever come up with a way to make imports
|
||||
* work.
|
||||
function testDisableDuplicateStyleSheetImports() {
|
||||
var el1 = document.getElementById('devcss-test-importfixer-1');
|
||||
var el2 = document.getElementById('devcss-test-importfixer-2');
|
||||
|
||||
var backgroundColor = goog.style.getBackgroundColor(el1);
|
||||
assertEquals('rgb(255,255,0)', spaceless(backgroundColor));
|
||||
|
||||
var backgroundColor = goog.style.getBackgroundColor(el2);
|
||||
assertEquals('rgb(255,0,0)', spaceless(backgroundColor));
|
||||
|
||||
// This should disable the second coming of devcss_test_import_1.css.
|
||||
var devcssInstance = new goog.debug.DevCss();
|
||||
devcssInstance.disableDuplicateStyleSheetImports();
|
||||
|
||||
var backgroundColor = goog.style.getBackgroundColor(el1);
|
||||
assertEquals('rgb(255,255,0)', spaceless(backgroundColor));
|
||||
|
||||
var backgroundColor = goog.style.getBackgroundColor(el2);
|
||||
assertEquals('rgb(173,216,230)', spaceless(backgroundColor));
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Development CSS Compiler runtime execution.
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.devCssRunner');
|
||||
|
||||
goog.require('goog.debug.DevCss');
|
||||
|
||||
(function() {
|
||||
var devCssInstance = new goog.debug.DevCss();
|
||||
devCssInstance.activateBrowserSpecificCssRules();
|
||||
})();
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Simple logger that logs a Div Element.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.DivConsole');
|
||||
|
||||
goog.require('goog.debug.HtmlFormatter');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.dom.safe');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.style');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for visualising logger calls in a div element.
|
||||
* @param {Element} element The element to append to.
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.DivConsole = function(element) {
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
this.formatter_ = new goog.debug.HtmlFormatter();
|
||||
this.formatter_.showAbsoluteTime = false;
|
||||
this.isCapturing_ = false;
|
||||
this.element_ = element;
|
||||
this.elementOwnerDocument_ =
|
||||
this.element_.ownerDocument || this.element_.document;
|
||||
|
||||
this.installStyles();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs styles for the log messages and its div
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.installStyles = function() {
|
||||
goog.style.installStyles(
|
||||
'.dbg-sev{color:#F00}' +
|
||||
'.dbg-w{color:#C40}' +
|
||||
'.dbg-sh{font-weight:bold;color:#000}' +
|
||||
'.dbg-i{color:#444}' +
|
||||
'.dbg-f{color:#999}' +
|
||||
'.dbg-ev{color:#0A0}' +
|
||||
'.dbg-m{color:#990}' +
|
||||
'.logmsg{border-bottom:1px solid #CCC;padding:2px}' +
|
||||
'.logsep{background-color: #8C8;}' +
|
||||
'.logdiv{border:1px solid #CCC;background-color:#FCFCFC;' +
|
||||
'font:medium monospace}',
|
||||
this.element_);
|
||||
this.element_.className += ' logdiv';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
this.logBuffer = '';
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord The log entry.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.addLogRecord = function(logRecord) {
|
||||
if (!logRecord) {
|
||||
return;
|
||||
}
|
||||
var scroll = this.element_.scrollHeight - this.element_.scrollTop -
|
||||
this.element_.clientHeight <= 100;
|
||||
|
||||
var div = this.elementOwnerDocument_.createElement('div');
|
||||
div.className = 'logmsg';
|
||||
goog.dom.safe.setInnerHtml(
|
||||
div, this.formatter_.formatRecordAsHtml(logRecord));
|
||||
this.element_.appendChild(div);
|
||||
|
||||
if (scroll) {
|
||||
this.element_.scrollTop = this.element_.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the formatter for outputting to the console. The default formatter
|
||||
* is an instance of goog.debug.HtmlFormatter
|
||||
* @return {!goog.debug.Formatter} The formatter in use.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the formatter for outputting to the console.
|
||||
* @param {goog.debug.HtmlFormatter} formatter The formatter to use.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.setFormatter = function(formatter) {
|
||||
this.formatter_ = formatter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a separator to the debug window.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.addSeparator = function() {
|
||||
var div = this.elementOwnerDocument_.createElement('div');
|
||||
div.className = 'logmsg logsep';
|
||||
this.element_.appendChild(div);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the console.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.clear = function() {
|
||||
if (this.element_) {
|
||||
goog.dom.safe.setInnerHtml(this.element_, goog.html.SafeHtml.EMPTY);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.enhanceError
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debugEnhanceErrorTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2007 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debugEnhanceErrorTest');
|
||||
goog.setTestOnly('goog.debugEnhanceErrorTest');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var THROW_STRING = 1;
|
||||
var THROW_NPE = 2;
|
||||
var THROW_ERROR = 3;
|
||||
var THROW_ENHANCED_ERROR = 4;
|
||||
var THROW_ENHANCED_STRING = 5;
|
||||
|
||||
if (typeof debug == 'undefined') {
|
||||
function debug(str) {
|
||||
if (window.console) window.console.log(str);
|
||||
}
|
||||
}
|
||||
|
||||
function testEnhanceError() {
|
||||
// Tests are like this:
|
||||
// [test num, expect something in the stack, expect an extra message]
|
||||
var tests = [
|
||||
[THROW_STRING],
|
||||
[THROW_NPE],
|
||||
[THROW_ERROR],
|
||||
[THROW_ENHANCED_ERROR, 'throwEnhancedError', 'an enhanced error'],
|
||||
[THROW_ENHANCED_STRING, 'throwEnhancedString']
|
||||
];
|
||||
for (var i = 0; i < tests.length; ++i) {
|
||||
var test = tests[i];
|
||||
var testNum = test[0];
|
||||
var testInStack = test[1];
|
||||
var testExtraMessage = test[2] || null;
|
||||
try {
|
||||
foo(testNum);
|
||||
} catch (e) {
|
||||
debug(goog.debug.expose(e));
|
||||
var s = e.stack.split('\n');
|
||||
for (var j = 0; j < s.length; ++j) {
|
||||
debug(s[j]);
|
||||
}
|
||||
// 'baz' is always in the stack
|
||||
assertTrue('stack should contain "baz"',
|
||||
e.stack.indexOf('baz') != -1);
|
||||
|
||||
if (testInStack) {
|
||||
assertTrue('stack should contain "' + testInStack + '"',
|
||||
e.stack.indexOf(testInStack) != -1);
|
||||
}
|
||||
if (testExtraMessage) {
|
||||
// 2 messages
|
||||
assertTrue('message0 should contain "' + testExtraMessage + '"',
|
||||
e.message0.indexOf(testExtraMessage) != -1);
|
||||
assertTrue('message1 should contain "message from baz"',
|
||||
e.message1.indexOf('message from baz') != -1);
|
||||
} else {
|
||||
// 1 message
|
||||
assertTrue('message0 should contain "message from baz"',
|
||||
e.message0.indexOf('message from baz') != -1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
fail('expected to catch an exception');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function foo(testNum) {
|
||||
bar(testNum);
|
||||
}
|
||||
|
||||
function bar(testNum) {
|
||||
baz(testNum);
|
||||
}
|
||||
|
||||
function baz(testNum) {
|
||||
try {
|
||||
switch (testNum) {
|
||||
case THROW_STRING:
|
||||
throwString();
|
||||
break;
|
||||
case THROW_NPE:
|
||||
throwNpe();
|
||||
break;
|
||||
case THROW_ERROR:
|
||||
throwError();
|
||||
break;
|
||||
case THROW_ENHANCED_ERROR:
|
||||
throwEnhancedError();
|
||||
break;
|
||||
case THROW_ENHANCED_STRING:
|
||||
throwEnhancedString();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
throw goog.debug.enhanceError(e, 'message from baz');
|
||||
}
|
||||
}
|
||||
|
||||
function throwString() {
|
||||
throw 'a string';
|
||||
}
|
||||
|
||||
function throwNpe() {
|
||||
var nada = null;
|
||||
nada.noSuchFunction();
|
||||
}
|
||||
|
||||
function throwError() {
|
||||
throw Error('an error');
|
||||
}
|
||||
|
||||
function throwEnhancedError() {
|
||||
throw goog.debug.enhanceError(Error('dang!'), 'an enhanced error');
|
||||
}
|
||||
|
||||
function throwEnhancedString() {
|
||||
throw goog.debug.enhanceError('oh nos!');
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2010 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview A global registry for entry points into a program,
|
||||
* so that they can be instrumented. Each module should register their
|
||||
* entry points with this registry. Designed to be compiled out
|
||||
* if no instrumentation is requested.
|
||||
*
|
||||
* Entry points may be registered before or after a call to
|
||||
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
|
||||
* later, the existing monitor will instrument the new entry point.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.EntryPointMonitor');
|
||||
goog.provide('goog.debug.entryPointRegistry');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
goog.debug.EntryPointMonitor = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Instruments a function.
|
||||
*
|
||||
* @param {!Function} fn A function to instrument.
|
||||
* @return {!Function} The instrumented function.
|
||||
*/
|
||||
goog.debug.EntryPointMonitor.prototype.wrap;
|
||||
|
||||
|
||||
/**
|
||||
* Try to remove an instrumentation wrapper created by this monitor.
|
||||
* If the function passed to unwrap is not a wrapper created by this
|
||||
* monitor, then we will do nothing.
|
||||
*
|
||||
* Notice that some wrappers may not be unwrappable. For example, if other
|
||||
* monitors have applied their own wrappers, then it will be impossible to
|
||||
* unwrap them because their wrappers will have captured our wrapper.
|
||||
*
|
||||
* So it is important that entry points are unwrapped in the reverse
|
||||
* order that they were wrapped.
|
||||
*
|
||||
* @param {!Function} fn A function to unwrap.
|
||||
* @return {!Function} The unwrapped function, or {@code fn} if it was not
|
||||
* a wrapped function created by this monitor.
|
||||
*/
|
||||
goog.debug.EntryPointMonitor.prototype.unwrap;
|
||||
|
||||
|
||||
/**
|
||||
* An array of entry point callbacks.
|
||||
* @type {!Array<function(!Function)>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.refList_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Monitors that should wrap all the entry points.
|
||||
* @type {!Array<!goog.debug.EntryPointMonitor>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitors_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
|
||||
* Checking this allows the compiler to optimize out the registrations.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Register an entry point with this module.
|
||||
*
|
||||
* The entry point will be instrumented when a monitor is passed to
|
||||
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
|
||||
* entry point is instrumented immediately.
|
||||
*
|
||||
* @param {function(!Function)} callback A callback function which is called
|
||||
* with a transforming function to instrument the entry point. The callback
|
||||
* is responsible for wrapping the relevant entry point with the
|
||||
* transforming function.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.register = function(callback) {
|
||||
// Don't use push(), so that this can be compiled out.
|
||||
goog.debug.entryPointRegistry.refList_[
|
||||
goog.debug.entryPointRegistry.refList_.length] = callback;
|
||||
// If no one calls monitorAll, this can be compiled out.
|
||||
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
|
||||
var monitors = goog.debug.entryPointRegistry.monitors_;
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
callback(goog.bind(monitors[i].wrap, monitors[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configures a monitor to wrap all entry points.
|
||||
*
|
||||
* Entry points that have already been registered are immediately wrapped by
|
||||
* the monitor. When an entry point is registered in the future, it will also
|
||||
* be wrapped by the monitor when it is registered.
|
||||
*
|
||||
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
|
||||
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
|
||||
var transformer = goog.bind(monitor.wrap, monitor);
|
||||
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
|
||||
goog.debug.entryPointRegistry.refList_[i](transformer);
|
||||
}
|
||||
goog.debug.entryPointRegistry.monitors_.push(monitor);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Try to unmonitor all the entry points that have already been registered. If
|
||||
* an entry point is registered in the future, it will not be wrapped by the
|
||||
* monitor when it is registered. Note that this may fail if the entry points
|
||||
* have additional wrapping.
|
||||
*
|
||||
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
|
||||
* the entry points.
|
||||
* @throws {Error} If the monitor is not the most recently configured monitor.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
|
||||
var monitors = goog.debug.entryPointRegistry.monitors_;
|
||||
goog.asserts.assert(monitor == monitors[monitors.length - 1],
|
||||
'Only the most recent monitor can be unwrapped.');
|
||||
var transformer = goog.bind(monitor.unwrap, monitor);
|
||||
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
|
||||
goog.debug.entryPointRegistry.refList_[i](transformer);
|
||||
}
|
||||
monitors.length--;
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
Author: nicksantos@google.com (Nick Santos)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.entryPointRegistry
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.entryPointRegistryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2010 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.entryPointRegistryTest');
|
||||
goog.setTestOnly('goog.debug.entryPointRegistryTest');
|
||||
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var lastError;
|
||||
var errorHandler;
|
||||
var errorFn;
|
||||
|
||||
function setUp() {
|
||||
lastError = null;
|
||||
errorFn = function(message) {
|
||||
throw {message: message};
|
||||
};
|
||||
errorHandler = new goog.debug.ErrorHandler(function(ex) {
|
||||
lastError = ex;
|
||||
});
|
||||
goog.debug.entryPointRegistry.refList_ = [];
|
||||
}
|
||||
|
||||
function testMonitorAndUnmonitor() {
|
||||
goog.debug.entryPointRegistry.register(function(transformer) {
|
||||
errorFn = transformer(errorFn);
|
||||
});
|
||||
goog.debug.entryPointRegistry.monitorAll(errorHandler);
|
||||
|
||||
var e = assertThrows('expected error', goog.partial(errorFn, 'Hello!'));
|
||||
assertEquals('Error in protected function: Hello!', e.message);
|
||||
assertEquals('Hello!', lastError.message);
|
||||
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible(errorHandler);
|
||||
|
||||
e = assertThrows('expected error', goog.partial(errorFn, 'Goodbye!'));
|
||||
assertEquals('Goodbye!', e.message);
|
||||
assertEquals('Hello!', lastError.message);
|
||||
}
|
||||
|
||||
function testRegisterAfterMonitor() {
|
||||
goog.debug.entryPointRegistry.monitorAll(errorHandler);
|
||||
goog.debug.entryPointRegistry.register(function(transformer) {
|
||||
errorFn = transformer(errorFn);
|
||||
});
|
||||
|
||||
var e = assertThrows('expected error', goog.partial(errorFn, 'Hello!'));
|
||||
assertEquals('Error in protected function: Hello!', e.message);
|
||||
assertEquals('Hello!', lastError.message);
|
||||
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible(errorHandler);
|
||||
|
||||
e = assertThrows('expected error', goog.partial(errorFn, 'Goodbye!'));
|
||||
assertEquals('Goodbye!', e.message);
|
||||
assertEquals('Hello!', lastError.message);
|
||||
}
|
||||
|
||||
function testInvalidUnmonitor() {
|
||||
goog.debug.entryPointRegistry.monitorAll(errorHandler);
|
||||
var e = assertThrows('expected error', goog.partial(
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible,
|
||||
new goog.debug.ErrorHandler()));
|
||||
assertEquals(
|
||||
'Assertion failed: Only the most recent monitor can be unwrapped.',
|
||||
e.message);
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible(errorHandler);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2009 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a base class for custom Error objects such that the
|
||||
* stack is correctly maintained.
|
||||
*
|
||||
* You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
|
||||
* sufficient.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Error');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for custom error objects.
|
||||
* @param {*=} opt_msg The message associated with the error.
|
||||
* @constructor
|
||||
* @extends {Error}
|
||||
*/
|
||||
goog.debug.Error = function(opt_msg) {
|
||||
|
||||
// Attempt to ensure there is a stack trace.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, goog.debug.Error);
|
||||
} else {
|
||||
var stack = new Error().stack;
|
||||
if (stack) {
|
||||
this.stack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_msg) {
|
||||
this.message = String(opt_msg);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.Error, Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.Error.prototype.name = 'CustomError';
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.Error
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.ErrorTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2009 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.ErrorTest');
|
||||
goog.setTestOnly('goog.debug.ErrorTest');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.testing.ExpectedFailures');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent');
|
||||
goog.require('goog.userAgent.product');
|
||||
|
||||
var expectedFailures;
|
||||
|
||||
function setUpPage() {
|
||||
expectedFailures = new goog.testing.ExpectedFailures();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
expectedFailures.handleTearDown();
|
||||
}
|
||||
|
||||
function testError() {
|
||||
function xxxxx() {
|
||||
yyyyy();
|
||||
}
|
||||
function yyyyy() {
|
||||
zzzzz();
|
||||
}
|
||||
function zzzzz() {
|
||||
throw new goog.debug.Error('testing');
|
||||
}
|
||||
|
||||
var stack = null, message = null;
|
||||
try {
|
||||
xxxxx();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
if (e.stack) {
|
||||
stack = e.stack.split('\n');
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals('Message property should be set', 'testing', message);
|
||||
|
||||
expectedFailures.expectFailureFor(
|
||||
(goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10')) ||
|
||||
goog.userAgent.product.SAFARI || (
|
||||
goog.userAgent.product.CHROME &&
|
||||
!goog.userAgent.isVersionOrHigher(532)),
|
||||
'error.stack is not widely supported');
|
||||
|
||||
try {
|
||||
assertNotNull(stack);
|
||||
|
||||
if (goog.userAgent.product.FIREFOX &&
|
||||
goog.userAgent.isVersionOrHigher('2.0')) {
|
||||
// Firefox 4 and greater does not have the first line that says
|
||||
// 'Error'. So we insert a dummy line to simplify the test.
|
||||
stack.splice(0, 0, 'Error');
|
||||
}
|
||||
|
||||
// If the stack trace came from a synthetic Error object created
|
||||
// inside the goog.debug.Error constructor, it will have an extra frame
|
||||
// at stack[1]. If it came from captureStackTrace or was attached
|
||||
// by IE when the error was caught, it will not.
|
||||
if (!Error.captureStackTrace && !goog.userAgent.IE) {
|
||||
stack.splice(1, 1); // Remove stack[1].
|
||||
}
|
||||
|
||||
assertContains(
|
||||
'1st line of stack should have "Error"', 'Error', stack[0]);
|
||||
assertContains(
|
||||
'2nd line of stack should have "zzzzz"', 'zzzzz', stack[1]);
|
||||
assertContains(
|
||||
'3rd line of stack should have "yyyyy"', 'yyyyy', stack[2]);
|
||||
assertContains(
|
||||
'4th line of stack should have "xxxxx"', 'xxxxx', stack[3]);
|
||||
} catch (e) {
|
||||
expectedFailures.handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
function testInheriting() {
|
||||
function MyError() {
|
||||
goog.debug.Error.call(this);
|
||||
}
|
||||
goog.inherits(MyError, goog.debug.Error);
|
||||
MyError.prototype.message = 'My custom error';
|
||||
|
||||
var message = null;
|
||||
try {
|
||||
throw new MyError();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
assertEquals('My custom error', message);
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright 2007 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Error handling utilities.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.ErrorHandler');
|
||||
goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.EntryPointMonitor');
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.debug.Trace');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The ErrorHandler can be used to to wrap functions with a try/catch
|
||||
* statement. If an exception is thrown, the given error handler function will
|
||||
* be called.
|
||||
*
|
||||
* When this object is disposed, it will stop handling exceptions and tracing.
|
||||
* It will also try to restore window.setTimeout and window.setInterval
|
||||
* if it wrapped them. Notice that in the general case, it is not technically
|
||||
* possible to remove the wrapper, because functions have no knowledge of
|
||||
* what they have been assigned to. So the app is responsible for other
|
||||
* forms of unwrapping.
|
||||
*
|
||||
* @param {Function} handler Handler for exceptions.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @implements {goog.debug.EntryPointMonitor}
|
||||
*/
|
||||
goog.debug.ErrorHandler = function(handler) {
|
||||
goog.debug.ErrorHandler.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Handler for exceptions, which can do logging, reporting, etc.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.errorHandlerFn_ = handler;
|
||||
|
||||
/**
|
||||
* Whether errors should be wrapped in
|
||||
* goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wrapErrors_ = true; // TODO(user) Change default.
|
||||
|
||||
/**
|
||||
* Whether to add a prefix to all error messages. The prefix is
|
||||
* goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option
|
||||
* only has an effect if this.wrapErrors_ is set to false.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.prefixErrorMessages_ = false;
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorHandler, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Whether to add tracers when instrumenting entry points.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Enable tracers when instrumenting entry points.
|
||||
* @param {boolean} newVal See above.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions =
|
||||
function(newVal) {
|
||||
this.addTracersToProtectedFunctions_ = newVal;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
|
||||
return this.protectEntryPoint(goog.asserts.assertFunction(fn));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
|
||||
goog.asserts.assertFunction(fn);
|
||||
return fn[this.getFunctionIndex_(false)] || fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to return a span that can be clicked on to display
|
||||
* an alert with the current stack trace. Newlines are replaced with a
|
||||
* placeholder so that they will not be html-escaped.
|
||||
* @param {string} stackTrace The stack trace to create a span for.
|
||||
* @return {string} A span which can be clicked on to show the stack trace.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {
|
||||
var buffer = [];
|
||||
buffer.push('##PE_STACK_START##');
|
||||
buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));
|
||||
buffer.push('##PE_STACK_END##');
|
||||
return buffer.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the index for a function. Used for internal indexing.
|
||||
* @param {boolean} wrapper True for the wrapper; false for the wrapped.
|
||||
* @return {string} The index where we should store the function in its
|
||||
* wrapper/wrapped function.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
|
||||
return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for an entry point function. When an exception
|
||||
* is thrown from a protected function, a handler will be invoked to handle it.
|
||||
*
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @return {!Function} A protected wrapper function that calls the entry point
|
||||
* function.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
|
||||
var protectedFnName = this.getFunctionIndex_(true);
|
||||
if (!fn[protectedFnName]) {
|
||||
var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
|
||||
wrapper[this.getFunctionIndex_(false)] = fn;
|
||||
}
|
||||
return fn[protectedFnName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helps {@link #protectEntryPoint} by actually creating the protected
|
||||
* wrapper function, after {@link #protectEntryPoint} determines that one does
|
||||
* not already exist for the given function. Can be overriden by subclasses
|
||||
* that may want to implement different error handling, or add additional
|
||||
* entry point hooks.
|
||||
* @param {!Function} fn An entry point function to be protected.
|
||||
* @return {!Function} protected wrapper function.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
|
||||
var that = this;
|
||||
var tracers = this.addTracersToProtectedFunctions_;
|
||||
if (tracers) {
|
||||
var stackTrace = goog.debug.getStacktraceSimple(15);
|
||||
}
|
||||
var googDebugErrorHandlerProtectedFunction = function() {
|
||||
if (that.isDisposed()) {
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (tracers) {
|
||||
var tracer = goog.debug.Trace.startTracer('protectedEntryPoint: ' +
|
||||
that.getStackTraceHolder_(stackTrace));
|
||||
}
|
||||
try {
|
||||
return fn.apply(this, arguments);
|
||||
} catch (e) {
|
||||
that.errorHandlerFn_(e);
|
||||
if (!that.wrapErrors_) {
|
||||
// Add the prefix to the existing message.
|
||||
if (that.prefixErrorMessages_) {
|
||||
if (typeof e === 'object') {
|
||||
e.message =
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
e.message;
|
||||
} else {
|
||||
e = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
e;
|
||||
}
|
||||
}
|
||||
if (goog.DEBUG) {
|
||||
// Work around for https://code.google.com/p/v8/issues/detail?id=2625
|
||||
// and https://code.google.com/p/chromium/issues/detail?id=237059
|
||||
// Custom errors and errors with custom stack traces show the wrong
|
||||
// stack trace
|
||||
// If it has a stack and Error.captureStackTrace is supported (only
|
||||
// supported in V8 as of May 2013) log the stack to the console.
|
||||
if (e && e.stack && Error.captureStackTrace &&
|
||||
goog.global['console']) {
|
||||
goog.global['console']['error'](e.message, e.stack);
|
||||
}
|
||||
}
|
||||
// Re-throw original error. This is great for debugging as it makes
|
||||
// browser JS dev consoles show the correct error and stack trace.
|
||||
throw e;
|
||||
}
|
||||
// Re-throw it since this may be expected by the caller.
|
||||
throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
|
||||
} finally {
|
||||
if (tracers) {
|
||||
goog.debug.Trace.stopTracer(tracer);
|
||||
}
|
||||
}
|
||||
};
|
||||
googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
|
||||
return googDebugErrorHandlerProtectedFunction;
|
||||
};
|
||||
|
||||
|
||||
// TODO(mknichel): Allow these functions to take in the window to protect.
|
||||
/**
|
||||
* Installs exception protection for window.setTimeout to handle exceptions.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowSetTimeout =
|
||||
function() {
|
||||
this.protectWindowFunctionsHelper_('setTimeout');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Install exception protection for window.setInterval to handle exceptions.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowSetInterval =
|
||||
function() {
|
||||
this.protectWindowFunctionsHelper_('setInterval');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Install exception protection for window.requestAnimationFrame to handle
|
||||
* exceptions.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =
|
||||
function() {
|
||||
var win = goog.getObjectByName('window');
|
||||
var fnNames = [
|
||||
'requestAnimationFrame',
|
||||
'mozRequestAnimationFrame',
|
||||
'webkitAnimationFrame',
|
||||
'msRequestAnimationFrame'
|
||||
];
|
||||
for (var i = 0; i < fnNames.length; i++) {
|
||||
var fnName = fnNames[i];
|
||||
if (fnNames[i] in win) {
|
||||
this.protectWindowFunctionsHelper_(fnName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for protecting a function that causes a function to be
|
||||
* asynchronously called, for example setTimeout or requestAnimationFrame.
|
||||
* @param {string} fnName The name of the function to protect.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ =
|
||||
function(fnName) {
|
||||
var win = goog.getObjectByName('window');
|
||||
var originalFn = win[fnName];
|
||||
var that = this;
|
||||
win[fnName] = function(fn, time) {
|
||||
// Don't try to protect strings. In theory, we could try to globalEval
|
||||
// the string, but this seems to lead to permission errors on IE6.
|
||||
if (goog.isString(fn)) {
|
||||
fn = goog.partial(goog.globalEval, fn);
|
||||
}
|
||||
fn = that.protectEntryPoint(fn);
|
||||
|
||||
// IE doesn't support .call for setInterval/setTimeout, but it
|
||||
// also doesn't care what "this" is, so we can just call the
|
||||
// original function directly
|
||||
if (originalFn.call) {
|
||||
return originalFn.call(this, fn, time);
|
||||
} else {
|
||||
return originalFn(fn, time);
|
||||
}
|
||||
};
|
||||
win[fnName][this.getFunctionIndex_(false)] = originalFn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to wrap errors that occur in protected functions in a
|
||||
* goog.debug.ErrorHandler.ProtectedFunctionError.
|
||||
* @param {boolean} wrapErrors Whether to wrap errors.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {
|
||||
this.wrapErrors_ = wrapErrors;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to add a prefix to all error messages that occur in protected
|
||||
* functions.
|
||||
* @param {boolean} prefixErrorMessages Whether to add a prefix to error
|
||||
* messages.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.setPrefixErrorMessages =
|
||||
function(prefixErrorMessages) {
|
||||
this.prefixErrorMessages_ = prefixErrorMessages;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
|
||||
// Try to unwrap window.setTimeout and window.setInterval.
|
||||
var win = goog.getObjectByName('window');
|
||||
win.setTimeout = this.unwrap(win.setTimeout);
|
||||
win.setInterval = this.unwrap(win.setInterval);
|
||||
|
||||
goog.debug.ErrorHandler.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown to the caller of a protected entry point if the entry point
|
||||
* throws an error.
|
||||
* @param {*} cause The error thrown by the entry point.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
|
||||
var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
(cause && cause.message ? String(cause.message) : String(cause));
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.base(
|
||||
this, 'constructor', message);
|
||||
|
||||
/**
|
||||
* The error thrown by the entry point.
|
||||
* @type {*}
|
||||
*/
|
||||
this.cause = cause;
|
||||
|
||||
var stack = cause && cause.stack;
|
||||
if (stack && goog.isString(stack)) {
|
||||
this.stack = /** @type {string} */ (stack);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);
|
||||
|
||||
|
||||
/**
|
||||
* Text to prefix the message with.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
|
||||
'Error in protected function: ';
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.ErrorHandler async tests
|
||||
</title>
|
||||
<!-- These paths should be relative to the test -->
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.ErrorHandlerAsyncTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.ErrorHandlerAsyncTest');
|
||||
goog.setTestOnly('goog.debug.ErrorHandlerAsyncTest');
|
||||
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var testCase = new goog.testing.AsyncTestCase(document.title);
|
||||
|
||||
testCase.setUpPage = function() {
|
||||
this.waitForAsync('setUpPage');
|
||||
this.stepTimeout = 5 * 1000;
|
||||
|
||||
this.oldTimeout = window.setTimeout;
|
||||
this.oldInterval = window.setInterval;
|
||||
this.oldRequestAnimationFrame = window.requestAnimationFrame;
|
||||
|
||||
// Whether requestAnimationFrame is available for testing.
|
||||
this.testingReqAnimFrame = !!window.requestAnimationFrame;
|
||||
|
||||
this.handler = new goog.debug.ErrorHandler(
|
||||
goog.bind(this.onException, this));
|
||||
this.handler.protectWindowSetTimeout();
|
||||
this.handler.protectWindowSetInterval();
|
||||
this.handler.protectWindowRequestAnimationFrame();
|
||||
this.exceptions = [];
|
||||
this.errors = 0;
|
||||
|
||||
// Override the error event handler, since we are purposely throwing
|
||||
// exceptions from global functions, and expect them
|
||||
this.oldWindowOnError = window.onerror;
|
||||
window.onerror = goog.bind(this.onError, this);
|
||||
|
||||
window.setTimeout(goog.bind(this.timeOut, this), 10);
|
||||
this.intervalId = window.setInterval(goog.bind(this.interval, this), 20);
|
||||
|
||||
if (this.testingReqAnimFrame) {
|
||||
window.requestAnimationFrame(goog.bind(this.animFrame, this));
|
||||
}
|
||||
};
|
||||
|
||||
testCase.tearDownPage = function() {
|
||||
window.setTimeout = this.oldTimeout;
|
||||
window.setInterval = this.oldInterval;
|
||||
window.requestAnimationFrame = this.oldRequestAnimationFrame;
|
||||
};
|
||||
|
||||
testCase.onException = function(e) {
|
||||
this.exceptions.push(e);
|
||||
if (this.timeoutHit && this.intervalHit &&
|
||||
(!this.testingReqAnimFrame || this.animFrameHit)) {
|
||||
this.continueTesting();
|
||||
}
|
||||
};
|
||||
|
||||
testCase.onError = function(msg, url, line) {
|
||||
this.errors++;
|
||||
return true;
|
||||
};
|
||||
|
||||
testCase.timeOut = function() {
|
||||
this.timeoutHit = true;
|
||||
throw arguments.callee;
|
||||
};
|
||||
|
||||
testCase.interval = function() {
|
||||
this.intervalHit = true;
|
||||
window.clearTimeout(this.intervalId);
|
||||
throw arguments.callee;
|
||||
};
|
||||
|
||||
testCase.animFrame = function() {
|
||||
this.animFrameHit = true;
|
||||
throw arguments.callee;
|
||||
};
|
||||
|
||||
testCase.addNewTest('testResults', function() {
|
||||
var timeoutHit, intervalHit, animFrameHit;
|
||||
|
||||
for (var i = 0; i < this.exceptions.length; ++i) {
|
||||
switch (this.exceptions[i]) {
|
||||
case this.timeOut: timeoutHit = true; break;
|
||||
case this.interval: intervalHit = true; break;
|
||||
case this.animFrame: animFrameHit = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue('timeout exception not received', timeoutHit);
|
||||
assertTrue('timeout not called', this.timeoutHit);
|
||||
assertTrue('interval exception not received', intervalHit);
|
||||
assertTrue('interval not called', this.intervalHit);
|
||||
if (this.testingReqAnimFrame) {
|
||||
assertTrue('anim frame exception not received', animFrameHit);
|
||||
assertTrue('animFrame not called', this.animFrameHit);
|
||||
}
|
||||
|
||||
if (!goog.userAgent.WEBKIT) {
|
||||
var expectedRethrownCount = this.testingReqAnimFrame ? 3 : 2;
|
||||
assertEquals(
|
||||
expectedRethrownCount + ' exceptions should have been rethrown',
|
||||
expectedRethrownCount, this.errors);
|
||||
}
|
||||
});
|
||||
|
||||
// Standalone Closure Test Runner.
|
||||
G_testRunner.initialize(testCase);
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.ErrorHandler
|
||||
</title>
|
||||
<!-- These paths should be relative to the test -->
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.ErrorHandlerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.ErrorHandlerTest');
|
||||
goog.setTestOnly('goog.debug.ErrorHandlerTest');
|
||||
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var oldGetObjectByName;
|
||||
|
||||
// provide our own window that implements our instrumented and
|
||||
// immediate-call versions of setTimeout and setInterval
|
||||
var fakeWin = {};
|
||||
|
||||
var errorHandler;
|
||||
var mockControl;
|
||||
|
||||
function badTimer() {
|
||||
arguments.callee.called = true;
|
||||
throw 'die die die';
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
// On IE, globalEval happens async. So make it synchronous.
|
||||
goog.globalEval = function(str) {
|
||||
eval(str);
|
||||
};
|
||||
|
||||
oldGetObjectByName = goog.getObjectByName;
|
||||
goog.getObjectByName = function(name) {
|
||||
if (name == 'window') {
|
||||
return fakeWin;
|
||||
} else {
|
||||
return oldGetObjectByName(name);
|
||||
}
|
||||
};
|
||||
|
||||
fakeWin.setTimeout = function(fn, time) {
|
||||
fakeWin.setTimeout.called = true;
|
||||
fakeWin.setTimeout.that = this;
|
||||
if (goog.isString(fn)) {
|
||||
eval(fn);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
fakeWin.setInterval = function(fn, time) {
|
||||
fakeWin.setInterval.called = true;
|
||||
fakeWin.setInterval.that = this;
|
||||
if (goog.isString(fn)) {
|
||||
eval(fn);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
fakeWin.requestAnimationFrame = function(fn) {
|
||||
fakeWin.requestAnimationFrame.called = true;
|
||||
fakeWin.requestAnimationFrame.that = this;
|
||||
fn();
|
||||
};
|
||||
|
||||
// just record the exception in the error handler when it happens
|
||||
errorHandler = new goog.debug.ErrorHandler(
|
||||
function(ex) { this.ex = ex; });
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockControl.$tearDown();
|
||||
goog.dispose(errorHandler);
|
||||
errorHandler = null;
|
||||
|
||||
goog.getObjectByName = oldGetObjectByName;
|
||||
|
||||
delete badTimer['__protected__'];
|
||||
}
|
||||
|
||||
function testWrapSetTimeout() {
|
||||
errorHandler.protectWindowSetTimeout();
|
||||
|
||||
var caught;
|
||||
|
||||
try {
|
||||
fakeWin.setTimeout(badTimer, 3);
|
||||
} catch (ex) {
|
||||
caught = ex;
|
||||
}
|
||||
assertSetTimeoutError(caught);
|
||||
}
|
||||
|
||||
function testWrapSetTimeoutWithString() {
|
||||
errorHandler.protectWindowSetTimeout();
|
||||
|
||||
var caught;
|
||||
|
||||
try {
|
||||
fakeWin.setTimeout('badTimer()', 3);
|
||||
} catch (ex) {
|
||||
caught = ex;
|
||||
}
|
||||
assertSetTimeoutError(caught);
|
||||
}
|
||||
|
||||
function testWrapSetInterval() {
|
||||
errorHandler.protectWindowSetInterval();
|
||||
|
||||
var caught;
|
||||
|
||||
try {
|
||||
fakeWin.setInterval(badTimer, 3);
|
||||
} catch (ex) {
|
||||
caught = ex;
|
||||
}
|
||||
assertSetIntervalError(caught);
|
||||
}
|
||||
|
||||
function testWrapSetIntervalWithString() {
|
||||
errorHandler.protectWindowSetInterval();
|
||||
|
||||
var caught;
|
||||
|
||||
try {
|
||||
fakeWin.setInterval('badTimer()', 3);
|
||||
} catch (ex) {
|
||||
caught = ex;
|
||||
}
|
||||
assertSetIntervalError(caught);
|
||||
}
|
||||
|
||||
function testWrapRequestAnimationFrame() {
|
||||
errorHandler.protectWindowRequestAnimationFrame();
|
||||
|
||||
var caught;
|
||||
try {
|
||||
fakeWin.requestAnimationFrame(badTimer);
|
||||
} catch (ex) {
|
||||
caught = ex;
|
||||
}
|
||||
assertRequestAnimationFrameError(caught);
|
||||
}
|
||||
|
||||
function testDisposal() {
|
||||
fakeWin = goog.getObjectByName('window');
|
||||
var originalSetTimeout = fakeWin.setTimeout;
|
||||
var originalSetInterval = fakeWin.setInterval;
|
||||
|
||||
errorHandler.protectWindowSetTimeout();
|
||||
errorHandler.protectWindowSetInterval();
|
||||
|
||||
assertNotEquals(originalSetTimeout, fakeWin.setTimeout);
|
||||
assertNotEquals(originalSetInterval, fakeWin.setInterval);
|
||||
|
||||
errorHandler.dispose();
|
||||
|
||||
assertEquals(originalSetTimeout, fakeWin.setTimeout);
|
||||
assertEquals(originalSetInterval, fakeWin.setInterval);
|
||||
}
|
||||
|
||||
function testUnwrap() {
|
||||
var fn = function() {};
|
||||
var wrappedFn = errorHandler.wrap(fn);
|
||||
assertNotEquals(wrappedFn, fn);
|
||||
|
||||
assertEquals(fn, errorHandler.unwrap(fn));
|
||||
assertEquals(fn, errorHandler.unwrap(wrappedFn));
|
||||
}
|
||||
|
||||
function testStackPreserved() {
|
||||
var e;
|
||||
var hasStacks;
|
||||
function specialFunctionName() {
|
||||
var e = Error();
|
||||
hasStacks = !!e.stack;
|
||||
throw e;
|
||||
};
|
||||
var wrappedFn = errorHandler.wrap(specialFunctionName);
|
||||
try {
|
||||
wrappedFn();
|
||||
} catch (exception) {
|
||||
e = exception;
|
||||
}
|
||||
assertTrue(!!e);
|
||||
if (hasStacks) {
|
||||
assertContains('specialFunctionName', e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function testGetProtectedFunction() {
|
||||
var fn = function() {
|
||||
throw new Error('Foo');
|
||||
};
|
||||
var protectedFn = errorHandler.getProtectedFunction(fn);
|
||||
var e = assertThrows(protectedFn);
|
||||
assertTrue(e instanceof goog.debug.ErrorHandler.ProtectedFunctionError);
|
||||
assertEquals('Foo', e.cause.message);
|
||||
}
|
||||
|
||||
function testGetProtectedFunction_withoutWrappedErrors() {
|
||||
var shouldCallErrorLog = !!Error.captureStackTrace;
|
||||
if (shouldCallErrorLog) {
|
||||
mockControl.createMethodMock(goog.global.console,
|
||||
'error');
|
||||
}
|
||||
errorHandler.setWrapErrors(false);
|
||||
var fn = function() {
|
||||
var e = new Error('Foo');
|
||||
e.stack = 'STACK';
|
||||
throw e;
|
||||
};
|
||||
var protectedFn = errorHandler.getProtectedFunction(fn);
|
||||
if (shouldCallErrorLog) {
|
||||
goog.global.console.error('Foo', 'STACK');
|
||||
}
|
||||
mockControl.$replayAll();
|
||||
var e = assertThrows(protectedFn);
|
||||
mockControl.$verifyAll();
|
||||
assertTrue(e instanceof Error);
|
||||
assertEquals('Foo', e.message);
|
||||
assertEquals(e.stack, 'STACK');
|
||||
}
|
||||
|
||||
function testGetProtectedFunction_withoutWrappedErrorsWithMessagePrefix() {
|
||||
errorHandler.setWrapErrors(false);
|
||||
errorHandler.setPrefixErrorMessages(true);
|
||||
var fn = function() {
|
||||
throw new Error('Foo');
|
||||
};
|
||||
var protectedFn = errorHandler.getProtectedFunction(fn);
|
||||
var e = assertThrows(protectedFn);
|
||||
assertTrue(e instanceof Error);
|
||||
assertEquals(
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
'Foo', e.message);
|
||||
|
||||
var stringError = function() {
|
||||
throw 'String';
|
||||
};
|
||||
protectedFn = errorHandler.getProtectedFunction(stringError);
|
||||
e = assertThrows(protectedFn);
|
||||
assertEquals('string', typeof e);
|
||||
assertEquals(
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
'String', e);
|
||||
}
|
||||
|
||||
function assertSetTimeoutError(caught) {
|
||||
assertMethodCalledHelper('setTimeout', caught);
|
||||
}
|
||||
|
||||
function assertSetIntervalError(caught) {
|
||||
assertMethodCalledHelper('setInterval', caught);
|
||||
}
|
||||
|
||||
function assertRequestAnimationFrameError(caught) {
|
||||
assertMethodCalledHelper('requestAnimationFrame', caught);
|
||||
}
|
||||
|
||||
function assertMethodCalledHelper(method, caught) {
|
||||
assertTrue('exception not thrown', !!caught);
|
||||
assertEquals('exception not caught by error handler',
|
||||
caught.cause, errorHandler.ex);
|
||||
assertTrue('fake ' + method + ' not called',
|
||||
!!fakeWin[method].called);
|
||||
assertTrue('"this" not passed to original ' + method,
|
||||
fakeWin[method].that === fakeWin);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2008 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview File which defines dummy object to work around undefined
|
||||
* properties compiler warning for weak dependencies on
|
||||
* {@link goog.debug.ErrorHandler#protectEntryPoint}.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.errorHandlerWeakDep');
|
||||
|
||||
|
||||
/**
|
||||
* Dummy object to work around undefined properties compiler warning.
|
||||
* @type {!Object<string,Function>}
|
||||
*/
|
||||
goog.debug.errorHandlerWeakDep = {
|
||||
/**
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @param {boolean=} opt_tracers Whether to install tracers around the
|
||||
* fn.
|
||||
* @return {Function} A protected wrapper function that calls the
|
||||
* entry point function.
|
||||
*/
|
||||
protectEntryPoint: function(fn, opt_tracers) { return fn; }
|
||||
};
|
||||
@@ -0,0 +1,429 @@
|
||||
// Copyright 2009 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the ErrorReporter class, which creates an error
|
||||
* handler that reports any errors raised to a URL.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.ErrorReporter');
|
||||
goog.provide('goog.debug.ErrorReporter.ExceptionEvent');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.uri.utils');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an error reporter. Internal Use Only. To install an error
|
||||
* reporter see the {@see #install} method below.
|
||||
*
|
||||
* @param {string} handlerUrl The URL to which all errors will be reported.
|
||||
* @param {function(!Error, !Object<string, string>)=}
|
||||
* opt_contextProvider When a report is to be sent to the server,
|
||||
* this method will be called, and given an opportunity to modify the
|
||||
* context object before submission to the server.
|
||||
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
|
||||
* onerror and to protect entry points. If apps have other error reporting
|
||||
* facilities, it may make sense for them to set these up themselves and use
|
||||
* the ErrorReporter just for transmission of reports.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.debug.ErrorReporter = function(
|
||||
handlerUrl, opt_contextProvider, opt_noAutoProtect) {
|
||||
goog.debug.ErrorReporter.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Context provider, if one was provided.
|
||||
* @type {?function(!Error, !Object<string, string>)}
|
||||
* @private
|
||||
*/
|
||||
this.contextProvider_ = opt_contextProvider || null;
|
||||
|
||||
/**
|
||||
* The string prefix of any optional context parameters logged with the error.
|
||||
* @private {string}
|
||||
*/
|
||||
this.contextPrefix_ = 'context.';
|
||||
|
||||
/**
|
||||
* The number of bytes after which the ErrorReporter truncates the POST body.
|
||||
* If null, the ErrorReporter won't truncate the body.
|
||||
* @private {?number}
|
||||
*/
|
||||
this.truncationLimit_ = null;
|
||||
|
||||
/**
|
||||
* Additional arguments to append to URL before sending XHR.
|
||||
* @private {!Object<string,string>}
|
||||
*/
|
||||
this.additionalArguments_ = {};
|
||||
|
||||
/**
|
||||
* XHR sender.
|
||||
* @type {function(string, string, string, (Object|goog.structs.Map)=)}
|
||||
* @private
|
||||
*/
|
||||
this.xhrSender_ = goog.debug.ErrorReporter.defaultXhrSender;
|
||||
|
||||
/**
|
||||
* The URL at which all errors caught by this handler will be logged.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.handlerUrl_ = handlerUrl;
|
||||
|
||||
if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
|
||||
if (!opt_noAutoProtect) {
|
||||
/**
|
||||
* The internal error handler used to catch all errors.
|
||||
*
|
||||
* @private {goog.debug.ErrorHandler}
|
||||
*/
|
||||
this.errorHandler_ = null;
|
||||
|
||||
this.setup_();
|
||||
}
|
||||
} else if (!opt_noAutoProtect) {
|
||||
goog.asserts.fail(
|
||||
'opt_noAutoProtect cannot be false while ' +
|
||||
'goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT is false. Setting ' +
|
||||
'ALLOW_AUTO_PROTECT to false removes the necessary auto-protect code ' +
|
||||
'in compiled/optimized mode.');
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorReporter, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, the code that provides additional entry point
|
||||
* protection and setup is exposed in this file. Set to false to avoid
|
||||
* bringing in a lot of code from ErrorHandler and entryPointRegistry in
|
||||
* compiled mode.
|
||||
*/
|
||||
goog.define('goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT', true);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event broadcast when an exception is logged.
|
||||
* @param {Error} error The exception that was was reported.
|
||||
* @param {!Object<string, string>} context The context values sent to the
|
||||
* server alongside this error.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.ErrorReporter.ExceptionEvent = function(error, context) {
|
||||
goog.events.Event.call(this, goog.debug.ErrorReporter.ExceptionEvent.TYPE);
|
||||
|
||||
/**
|
||||
* The error that was reported.
|
||||
* @type {Error}
|
||||
*/
|
||||
this.error = error;
|
||||
|
||||
/**
|
||||
* Context values sent to the server alongside this report.
|
||||
* @type {!Object<string, string>}
|
||||
*/
|
||||
this.context = context;
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorReporter.ExceptionEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Event type for notifying of a logged exception.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE =
|
||||
goog.events.getUniqueId('exception');
|
||||
|
||||
|
||||
/**
|
||||
* Extra headers for the error-reporting XHR.
|
||||
* @type {Object|goog.structs.Map|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.extraHeaders_;
|
||||
|
||||
|
||||
/**
|
||||
* Logging object.
|
||||
*
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.logger_ =
|
||||
goog.log.getLogger('goog.debug.ErrorReporter');
|
||||
|
||||
|
||||
/**
|
||||
* Installs an error reporter to catch all JavaScript errors raised.
|
||||
*
|
||||
* @param {string} loggingUrl The URL to which the errors caught will be
|
||||
* reported.
|
||||
* @param {function(!Error, !Object<string, string>)=}
|
||||
* opt_contextProvider When a report is to be sent to the server,
|
||||
* this method will be called, and given an opportunity to modify the
|
||||
* context object before submission to the server.
|
||||
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
|
||||
* onerror and to protect entry points. If apps have other error reporting
|
||||
* facilities, it may make sense for them to set these up themselves and use
|
||||
* the ErrorReporter just for transmission of reports.
|
||||
* @return {!goog.debug.ErrorReporter} The error reporter.
|
||||
*/
|
||||
goog.debug.ErrorReporter.install = function(
|
||||
loggingUrl, opt_contextProvider, opt_noAutoProtect) {
|
||||
var instance = new goog.debug.ErrorReporter(
|
||||
loggingUrl, opt_contextProvider, opt_noAutoProtect);
|
||||
return instance;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of XHR sender interface.
|
||||
*
|
||||
* @param {string} uri URI to make request to.
|
||||
* @param {string} method Send method.
|
||||
* @param {string} content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
*/
|
||||
goog.debug.ErrorReporter.defaultXhrSender = function(uri, method, content,
|
||||
opt_headers) {
|
||||
goog.net.XhrIo.send(uri, null, method, content, opt_headers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for an entry point function in addition
|
||||
* to those that are protected by default.
|
||||
* Has no effect in IE because window.onerror is used for reporting
|
||||
* exceptions in that case.
|
||||
*
|
||||
* @this {goog.debug.ErrorReporter}
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @return {Function} A protected wrapper function that calls the entry point
|
||||
* function or null if the entry point could not be protected.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.protectAdditionalEntryPoint =
|
||||
goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT ?
|
||||
function(fn) {
|
||||
if (this.errorHandler_) {
|
||||
return this.errorHandler_.protectEntryPoint(fn);
|
||||
}
|
||||
return null;
|
||||
} :
|
||||
function(fn) {
|
||||
goog.asserts.fail(
|
||||
'Cannot call protectAdditionalEntryPoint while ALLOW_AUTO_PROTECT ' +
|
||||
'is false. If ALLOW_AUTO_PROTECT is false, the necessary ' +
|
||||
'auto-protect code in compiled/optimized mode is removed.');
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
|
||||
/**
|
||||
* Sets up the error reporter.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setup_ = function() {
|
||||
if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('10')) {
|
||||
// Use "onerror" because caught exceptions in IE don't provide line
|
||||
// number.
|
||||
goog.debug.catchErrors(
|
||||
goog.bind(this.handleException, this), false, null);
|
||||
} else {
|
||||
// "onerror" doesn't work with FF2 or Chrome
|
||||
this.errorHandler_ = new goog.debug.ErrorHandler(
|
||||
goog.bind(this.handleException, this));
|
||||
|
||||
this.errorHandler_.protectWindowSetTimeout();
|
||||
this.errorHandler_.protectWindowSetInterval();
|
||||
this.errorHandler_.protectWindowRequestAnimationFrame();
|
||||
goog.debug.entryPointRegistry.monitorAll(this.errorHandler_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add headers to the logging url.
|
||||
* @param {Object|goog.structs.Map} loggingHeaders Extra headers to send
|
||||
* to the logging URL.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setLoggingHeaders =
|
||||
function(loggingHeaders) {
|
||||
this.extraHeaders_ = loggingHeaders;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the function used to send error reports to the server.
|
||||
* @param {function(string, string, string, (Object|goog.structs.Map)=)}
|
||||
* xhrSender If provided, this will be used to send a report to the
|
||||
* server instead of the default method. The function will be given the URI,
|
||||
* HTTP method request content, and (optionally) request headers to be
|
||||
* added.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setXhrSender = function(xhrSender) {
|
||||
this.xhrSender_ = xhrSender;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for caught exceptions. Sends report to the LoggingServlet and
|
||||
* notifies any listeners.
|
||||
*
|
||||
* @param {Object} e The exception.
|
||||
* @param {!Object<string, string>=} opt_context Context values to optionally
|
||||
* include in the error report.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.handleException = function(e,
|
||||
opt_context) {
|
||||
var error = /** @type {!Error} */ (goog.debug.normalizeErrorObject(e));
|
||||
|
||||
// Construct the context, possibly from the one provided in the argument, and
|
||||
// pass it to the context provider if there is one.
|
||||
var context = opt_context ? goog.object.clone(opt_context) : {};
|
||||
if (this.contextProvider_) {
|
||||
try {
|
||||
this.contextProvider_(error, context);
|
||||
} catch (err) {
|
||||
goog.log.error(goog.debug.ErrorReporter.logger_,
|
||||
'Context provider threw an exception: ' + err.message);
|
||||
}
|
||||
}
|
||||
// Truncate message to a reasonable length, since it will be sent in the URL.
|
||||
var message = error.message.substring(0, 2000);
|
||||
this.sendErrorReport(message, error.fileName, error.lineNumber, error.stack,
|
||||
context);
|
||||
|
||||
try {
|
||||
this.dispatchEvent(
|
||||
new goog.debug.ErrorReporter.ExceptionEvent(error, context));
|
||||
} catch (ex) {
|
||||
// Swallow exception to avoid infinite recursion.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends an error report to the logging URL. This will not consult the context
|
||||
* provider, the report will be sent exactly as specified.
|
||||
*
|
||||
* @param {string} message Error description.
|
||||
* @param {string} fileName URL of the JavaScript file with the error.
|
||||
* @param {number} line Line number of the error.
|
||||
* @param {string=} opt_trace Call stack trace of the error.
|
||||
* @param {!Object<string, string>=} opt_context Context information to include
|
||||
* in the request.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.sendErrorReport =
|
||||
function(message, fileName, line, opt_trace, opt_context) {
|
||||
try {
|
||||
// Create the logging URL.
|
||||
var requestUrl = goog.uri.utils.appendParams(this.handlerUrl_,
|
||||
'script', fileName, 'error', message, 'line', line);
|
||||
|
||||
if (!goog.object.isEmpty(this.additionalArguments_)) {
|
||||
requestUrl = goog.uri.utils.appendParamsFromMap(requestUrl,
|
||||
this.additionalArguments_);
|
||||
}
|
||||
|
||||
var queryMap = {};
|
||||
queryMap['trace'] = opt_trace;
|
||||
|
||||
// Copy context into query data map
|
||||
if (opt_context) {
|
||||
for (var entry in opt_context) {
|
||||
queryMap[this.contextPrefix_ + entry] = opt_context[entry];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy query data map into request.
|
||||
var queryData = goog.uri.utils.buildQueryDataFromMap(queryMap);
|
||||
|
||||
// Truncate if truncationLimit set.
|
||||
if (goog.isNumber(this.truncationLimit_)) {
|
||||
queryData = queryData.substring(0, this.truncationLimit_);
|
||||
}
|
||||
|
||||
// Send the request with the contents of the error.
|
||||
this.xhrSender_(requestUrl, 'POST', queryData, this.extraHeaders_);
|
||||
} catch (e) {
|
||||
var logMessage = goog.string.buildString(
|
||||
'Error occurred in sending an error report.\n\n',
|
||||
'script:', fileName, '\n',
|
||||
'line:', line, '\n',
|
||||
'error:', message, '\n',
|
||||
'trace:', opt_trace);
|
||||
goog.log.info(goog.debug.ErrorReporter.logger_, logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} prefix The prefix to appear prepended to all context
|
||||
* variables in the error report body.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setContextPrefix = function(prefix) {
|
||||
this.contextPrefix_ = prefix;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?number} limit Size in bytes to begin truncating POST body. Set to
|
||||
* null to prevent truncation. The limit must be >= 0.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setTruncationLimit = function(limit) {
|
||||
goog.asserts.assert(!goog.isNumber(limit) || limit >= 0,
|
||||
'Body limit must be valid number >= 0 or null');
|
||||
this.truncationLimit_ = limit;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Object<string,string>} urlArgs Set of key-value pairs to append
|
||||
* to handlerUrl_ before sending XHR.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setAdditionalArguments = function(urlArgs) {
|
||||
this.additionalArguments_ = urlArgs;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorReporter.prototype.disposeInternal = function() {
|
||||
if (goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT) {
|
||||
goog.dispose(this.errorHandler_);
|
||||
}
|
||||
goog.debug.ErrorReporter.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.ErrorReporter
|
||||
</title>
|
||||
<!-- These paths should be relative to the test -->
|
||||
<script src="../base.js" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.debug.ErrorReporterTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,388 @@
|
||||
// Copyright 2009 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.ErrorReporterTest');
|
||||
goog.setTestOnly('goog.debug.ErrorReporterTest');
|
||||
|
||||
goog.require('goog.debug.ErrorReporter');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
MockXhrIo = function() {};
|
||||
|
||||
MockXhrIo.prototype.onReadyStateChangeEntryPoint_ = function() {};
|
||||
|
||||
MockXhrIo.protectEntryPoints = function() {};
|
||||
|
||||
MockXhrIo.lastUrl = null;
|
||||
|
||||
MockXhrIo.send = function(url, opt_callback, opt_method, opt_content,
|
||||
opt_headers, opt_timeInterval) {
|
||||
MockXhrIo.lastUrl = url;
|
||||
MockXhrIo.lastContent = opt_content;
|
||||
MockXhrIo.lastHeaders = opt_headers;
|
||||
};
|
||||
|
||||
var errorReporter;
|
||||
var originalSetTimeout = window.setTimeout;
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
var url = 'http://www.your.tst/more/bogus.js';
|
||||
var encodedUrl = 'http%3A%2F%2Fwww.your.tst%2Fmore%2Fbogus.js';
|
||||
|
||||
function setUp() {
|
||||
stubs.set(goog.net, 'XhrIo', MockXhrIo);
|
||||
goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT = true;
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(errorReporter);
|
||||
stubs.reset();
|
||||
MockXhrIo.lastUrl = null;
|
||||
}
|
||||
|
||||
function throwAnErrorWith(script, line, message, opt_stack) {
|
||||
var error = {
|
||||
message: message,
|
||||
fileName: script,
|
||||
lineNumber: line};
|
||||
if (opt_stack) {
|
||||
error['stack'] = opt_stack;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
function testsendErrorReport() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace');
|
||||
|
||||
assertEquals('/log?script=filename.js&error=message&line=123',
|
||||
MockXhrIo.lastUrl);
|
||||
assertEquals('trace=trace', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testsendErrorReportWithCustomSender() {
|
||||
var uri = null;
|
||||
var method = null;
|
||||
var content = null;
|
||||
var headers = null;
|
||||
function mockXhrSender(uriIn, methodIn, contentIn, headersIn) {
|
||||
uri = uriIn;
|
||||
method = methodIn;
|
||||
content = contentIn;
|
||||
headers = headersIn;
|
||||
}
|
||||
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setXhrSender(mockXhrSender);
|
||||
errorReporter.sendErrorReport('message', 'filename.js', 123, 'trace');
|
||||
|
||||
assertEquals('/log?script=filename.js&error=message&line=123', uri);
|
||||
assertEquals('POST', method);
|
||||
assertEquals('trace=trace', content);
|
||||
assertUndefined(headers);
|
||||
}
|
||||
|
||||
function testsendErrorReport_noTrace() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123);
|
||||
|
||||
assertEquals('/log?script=filename.js&error=message&line=123',
|
||||
MockXhrIo.lastUrl);
|
||||
assertEquals('', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function test_nonInternetExplorerSendErrorReport() {
|
||||
stubs.set(goog.userAgent, 'IE', false);
|
||||
stubs.set(goog.global, 'setTimeout',
|
||||
function(fcn, time) {
|
||||
fcn.call();
|
||||
});
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
|
||||
var errorFunction = goog.partial(throwAnErrorWith, url, 5, 'Hello :)');
|
||||
|
||||
try {
|
||||
goog.global.setTimeout(errorFunction, 0);
|
||||
} catch (e) {
|
||||
// Expected. The error is rethrown after sending.
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
'/errorreporter?script=' + encodedUrl + '&error=Hello%20%3A)&line=5',
|
||||
MockXhrIo.lastUrl);
|
||||
assertEquals('trace=Not%20available', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function test_internetExplorerSendErrorReport() {
|
||||
stubs.set(goog.userAgent, 'IE', true);
|
||||
stubs.set(goog.userAgent, 'isVersionOrHigher', goog.functions.FALSE);
|
||||
|
||||
// Remove test runner's onerror handler so the test doesn't fail.
|
||||
stubs.set(goog.global, 'onerror', null);
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
goog.global.onerror('Goodbye :(', url, 22);
|
||||
assertEquals(
|
||||
'/errorreporter?script=' + encodedUrl + '&error=Goodbye%20%3A(&line=22',
|
||||
MockXhrIo.lastUrl);
|
||||
assertEquals('trace=Not%20available', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function test_setLoggingHeaders() {
|
||||
stubs.set(goog.userAgent, 'IE', true);
|
||||
stubs.set(goog.userAgent, 'isVersionOrHigher', goog.functions.FALSE);
|
||||
// Remove test runner's onerror handler so the test doesn't fail.
|
||||
stubs.set(goog.global, 'onerror', null);
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
errorReporter.setLoggingHeaders('header!');
|
||||
goog.global.onerror('Goodbye :(', 'http://www.your.tst/more/bogus.js', 22);
|
||||
assertEquals('header!', MockXhrIo.lastHeaders);
|
||||
}
|
||||
|
||||
function test_nonInternetExplorerSendErrorReportWithTrace() {
|
||||
stubs.set(goog.userAgent, 'IE', false);
|
||||
stubs.set(goog.global, 'setTimeout',
|
||||
function(fcn, time) {
|
||||
fcn.call();
|
||||
});
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
|
||||
var trace =
|
||||
'Error(\"Something Wrong\")@:0\n' +
|
||||
'$MF$E$Nx$([object Object])@http://a.b.c:83/a/f.js:901\n' +
|
||||
'([object Object])@http://a.b.c:813/a/f.js:37';
|
||||
|
||||
|
||||
var errorFunction = goog.partial(throwAnErrorWith, url, 5, 'Hello :)',
|
||||
trace);
|
||||
|
||||
try {
|
||||
goog.global.setTimeout(errorFunction, 0);
|
||||
} catch (e) {
|
||||
// Expected. The error is rethrown after sending.
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
'/errorreporter?script=' + encodedUrl + '&error=Hello%20%3A)&line=5',
|
||||
MockXhrIo.lastUrl);
|
||||
assertEquals('trace=' +
|
||||
'Error(%22Something%20Wrong%22)%40%3A0%0A' +
|
||||
'%24MF%24E%24Nx%24(%5Bobject%20Object%5D)%40' +
|
||||
'http%3A%2F%2Fa.b.c%3A83%2Fa%2Ff.js%3A901%0A' +
|
||||
'(%5Bobject%20Object%5D)%40http%3A%2F%2Fa.b.c%3A813%2Fa%2Ff.js%3A37',
|
||||
MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testProtectAdditionalEntryPoint_nonIE() {
|
||||
stubs.set(goog.userAgent, 'IE', false);
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
var fn = function() {};
|
||||
var protectedFn = errorReporter.protectAdditionalEntryPoint(fn);
|
||||
assertNotNull(protectedFn);
|
||||
assertNotEquals(fn, protectedFn);
|
||||
}
|
||||
|
||||
function testProtectAdditionalEntryPoint_IE() {
|
||||
stubs.set(goog.userAgent, 'IE', true);
|
||||
stubs.set(goog.userAgent, 'isVersionOrHigher', goog.functions.FALSE);
|
||||
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
var fn = function() {};
|
||||
var protectedFn = errorReporter.protectAdditionalEntryPoint(fn);
|
||||
assertNull(protectedFn);
|
||||
}
|
||||
|
||||
function testHandleException_dispatchesEvent() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
var loggedErrors = 0;
|
||||
goog.events.listen(errorReporter,
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE,
|
||||
function(event) {
|
||||
assertNotNullNorUndefined(event.error);
|
||||
loggedErrors++;
|
||||
});
|
||||
errorReporter.handleException(new Error());
|
||||
errorReporter.handleException(new Error());
|
||||
assertEquals('Expected 2 errors. ' +
|
||||
'(Ensure an exception was not swallowed.)', 2, loggedErrors);
|
||||
}
|
||||
|
||||
function testHandleException_includesContext() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
var loggedErrors = 0;
|
||||
var testError = new Error('test error');
|
||||
var testContext = { 'contextParam' : 'contextValue' };
|
||||
goog.events.listen(errorReporter,
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE,
|
||||
function(event) {
|
||||
assertNotNullNorUndefined(event.error);
|
||||
assertObjectEquals({ contextParam: 'contextValue' }, event.context);
|
||||
loggedErrors++;
|
||||
});
|
||||
errorReporter.handleException(testError, testContext);
|
||||
assertEquals('Expected 1 error. ' +
|
||||
'(Ensure an exception was not swallowed.)', 1, loggedErrors);
|
||||
}
|
||||
|
||||
function testContextProvider() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter',
|
||||
function(error, context) {
|
||||
context.providedContext = 'value';
|
||||
});
|
||||
var loggedErrors = 0;
|
||||
var testError = new Error('test error');
|
||||
goog.events.listen(errorReporter,
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE,
|
||||
function(event) {
|
||||
assertNotNullNorUndefined(event.error);
|
||||
assertObjectEquals({ providedContext: 'value' } , event.context);
|
||||
loggedErrors++;
|
||||
});
|
||||
errorReporter.handleException(testError);
|
||||
assertEquals('Expected 1 error. ' +
|
||||
'(Ensure an exception was not swallowed.)', 1, loggedErrors);
|
||||
}
|
||||
|
||||
function testContextProvider_withOtherContext() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter',
|
||||
function(error, context) {
|
||||
context.providedContext = 'value';
|
||||
});
|
||||
var loggedErrors = 0;
|
||||
var testError = new Error('test error');
|
||||
goog.events.listen(errorReporter,
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE,
|
||||
function(event) {
|
||||
assertNotNullNorUndefined(event.error);
|
||||
assertObjectEquals(
|
||||
{ providedContext: 'value', otherContext: 'value' },
|
||||
event.context);
|
||||
loggedErrors++;
|
||||
});
|
||||
errorReporter.handleException(testError, { 'otherContext' : 'value' });
|
||||
assertEquals('Expected 1 error. ' +
|
||||
'(Ensure an exception was not swallowed.)', 1, loggedErrors);
|
||||
}
|
||||
|
||||
function testHandleException_ignoresExceptionsDuringEventDispatch() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
goog.events.listen(errorReporter,
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE,
|
||||
function(event) {
|
||||
fail('This exception should be swallowed.');
|
||||
});
|
||||
errorReporter.handleException(new Error());
|
||||
}
|
||||
|
||||
function testDisposal() {
|
||||
errorReporter = goog.debug.ErrorReporter.install('/errorreporter');
|
||||
if (!goog.userAgent.IE) {
|
||||
assertNotEquals(originalSetTimeout, window.setTimeout);
|
||||
}
|
||||
goog.dispose(errorReporter);
|
||||
assertEquals(originalSetTimeout, window.setTimeout);
|
||||
}
|
||||
|
||||
function testSetContextPrefix() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setContextPrefix('baz.');
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('trace=trace&baz.foo=bar', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testTruncationLimit() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setTruncationLimit(6);
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('trace=', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testZeroTruncationLimit() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setTruncationLimit(0);
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testTruncationLimitLargerThanBody() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setTruncationLimit(9999);
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('trace=trace&context.foo=bar', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testSetNegativeTruncationLimit() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
assertThrows(function() {
|
||||
errorReporter.setTruncationLimit(-10);
|
||||
});
|
||||
}
|
||||
|
||||
function testSetTruncationLimitNull() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setTruncationLimit(null);
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('trace=trace&context.foo=bar', MockXhrIo.lastContent);
|
||||
}
|
||||
|
||||
function testAttemptAutoProtectWithAllowAutoProtectOff() {
|
||||
goog.debug.ErrorReporter.ALLOW_AUTO_PROTECT = false;
|
||||
assertThrows(function() {
|
||||
errorReporter = new goog.debug.ErrorReporter(
|
||||
'/log', function(e, context) {}, false);
|
||||
});
|
||||
}
|
||||
|
||||
function testSetAdditionalArgumentsArgsEmptyObject() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setAdditionalArguments({});
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('/log?script=filename.js&error=message&line=123',
|
||||
MockXhrIo.lastUrl);
|
||||
}
|
||||
|
||||
function testSetAdditionalArgumentsSingleArgument() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setAdditionalArguments({'extra': 'arg'});
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals('/log?script=filename.js&error=message&line=123&extra=arg',
|
||||
MockXhrIo.lastUrl);
|
||||
}
|
||||
|
||||
function testSetAdditionalArgumentsMultipleArguments() {
|
||||
errorReporter = new goog.debug.ErrorReporter('/log');
|
||||
errorReporter.setAdditionalArguments({'extra': 'arg', 'cat': 'dog'});
|
||||
errorReporter.sendErrorReport(
|
||||
'message', 'filename.js', 123, 'trace', {'foo': 'bar'});
|
||||
assertEquals(
|
||||
'/log?script=filename.js&error=message&line=123&extra=arg&cat=dog',
|
||||
MockXhrIo.lastUrl);
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the FancyWindow class. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
* This is a pretty hacky implementation, aimed at making debugging of large
|
||||
* applications more manageable.
|
||||
*
|
||||
* @see ../demos/debug.html
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.debug.FancyWindow');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug.DebugWindow');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.dom.DomHelper');
|
||||
goog.require('goog.dom.safe');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a Fancy extension to the DebugWindow class. Allows filtering based
|
||||
* on loggers and levels.
|
||||
*
|
||||
* @param {string=} opt_identifier Idenitifier for this logging class.
|
||||
* @param {string=} opt_prefix Prefix pre-pended to messages.
|
||||
* @constructor
|
||||
* @extends {goog.debug.DebugWindow}
|
||||
*/
|
||||
goog.debug.FancyWindow = function(opt_identifier, opt_prefix) {
|
||||
this.readOptionsFromLocalStorage_();
|
||||
goog.debug.FancyWindow.base(this, 'constructor', opt_identifier, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.FancyWindow, goog.debug.DebugWindow);
|
||||
|
||||
|
||||
/**
|
||||
* Constant indicating if we are able to use localStorage to persist filters
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.FancyWindow.HAS_LOCAL_STORE = (function() {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
return !!window['localStorage'].getItem;
|
||||
} catch (e) {}
|
||||
return false;
|
||||
})();
|
||||
|
||||
|
||||
/**
|
||||
* Constant defining the prefix to use when storing log levels
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.FancyWindow.LOCAL_STORE_PREFIX = 'fancywindow.sel.';
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.writeBufferToLog = function() {
|
||||
this.lastCall = goog.now();
|
||||
if (this.hasActiveWindow()) {
|
||||
var logel = this.dh_.getElement('log');
|
||||
|
||||
// Work out if scrolling is needed before we add the content
|
||||
var scroll =
|
||||
logel.scrollHeight - (logel.scrollTop + logel.offsetHeight) <= 100;
|
||||
|
||||
for (var i = 0; i < this.outputBuffer.length; i++) {
|
||||
var div = this.dh_.createDom('div', 'logmsg');
|
||||
div.innerHTML = this.outputBuffer[i];
|
||||
logel.appendChild(div);
|
||||
}
|
||||
this.outputBuffer.length = 0;
|
||||
this.resizeStuff_();
|
||||
|
||||
if (scroll) {
|
||||
logel.scrollTop = logel.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.writeInitialDocument = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var doc = this.win.document;
|
||||
doc.open();
|
||||
doc.write(this.getHtml_());
|
||||
doc.close();
|
||||
|
||||
(goog.userAgent.IE ? doc.body : this.win).onresize =
|
||||
goog.bind(this.resizeStuff_, this);
|
||||
|
||||
// Create a dom helper for the logging window
|
||||
this.dh_ = new goog.dom.DomHelper(doc);
|
||||
|
||||
// Don't use events system to reduce dependencies
|
||||
this.dh_.getElement('openbutton').onclick =
|
||||
goog.bind(this.openOptions_, this);
|
||||
this.dh_.getElement('closebutton').onclick =
|
||||
goog.bind(this.closeOptions_, this);
|
||||
this.dh_.getElement('clearbutton').onclick =
|
||||
goog.bind(this.clear, this);
|
||||
this.dh_.getElement('exitbutton').onclick =
|
||||
goog.bind(this.exit_, this);
|
||||
|
||||
this.writeSavedMessages();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the options menu.
|
||||
* @return {boolean} false.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.openOptions_ = function() {
|
||||
var el = this.dh_.getElement('optionsarea');
|
||||
goog.dom.safe.setInnerHtml(el, goog.html.SafeHtml.EMPTY);
|
||||
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var dh = this.dh_;
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var logger = loggers[i];
|
||||
var curlevel = logger.getLevel() ? logger.getLevel().name : 'INHERIT';
|
||||
var div = dh.createDom('div', {},
|
||||
this.getDropDown_('sel' + logger.getName(), curlevel),
|
||||
dh.createDom('span', {}, logger.getName() || '(root)'));
|
||||
el.appendChild(div);
|
||||
}
|
||||
|
||||
this.dh_.getElement('options').style.display = 'block';
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make a drop down for the log levels.
|
||||
* @param {string} id Logger id.
|
||||
* @param {string} selected What log level is currently selected.
|
||||
* @return {Element} The newly created 'select' DOM element.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.getDropDown_ = function(id, selected) {
|
||||
var dh = this.dh_;
|
||||
var sel = dh.createDom('select', {'id': id});
|
||||
var levels = goog.debug.Logger.Level.PREDEFINED_LEVELS;
|
||||
for (var i = 0; i < levels.length; i++) {
|
||||
var level = levels[i];
|
||||
var option = dh.createDom('option', {}, level.name);
|
||||
if (selected == level.name) {
|
||||
option.selected = true;
|
||||
}
|
||||
sel.appendChild(option);
|
||||
}
|
||||
sel.appendChild(dh.createDom('option',
|
||||
{'selected': selected == 'INHERIT'}, 'INHERIT'));
|
||||
return sel;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the options menu.
|
||||
* @return {boolean} The value false.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.closeOptions_ = function() {
|
||||
this.dh_.getElement('options').style.display = 'none';
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var dh = this.dh_;
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var logger = loggers[i];
|
||||
var sel = dh.getElement('sel' + logger.getName());
|
||||
var level = sel.options[sel.selectedIndex].text;
|
||||
if (level == 'INHERIT') {
|
||||
logger.setLevel(null);
|
||||
} else {
|
||||
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(level));
|
||||
}
|
||||
}
|
||||
this.writeOptionsToLocalStorage_();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the log elements
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.resizeStuff_ = function() {
|
||||
var dh = this.dh_;
|
||||
var logel = dh.getElement('log');
|
||||
var headel = dh.getElement('head');
|
||||
logel.style.top = headel.offsetHeight + 'px';
|
||||
logel.style.height = (dh.getDocument().body.offsetHeight -
|
||||
headel.offsetHeight - (goog.userAgent.IE ? 4 : 0)) + 'px';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the user clicking the exit button, disabled the debug window and
|
||||
* closes the popup.
|
||||
* @param {Event} e Event object.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.exit_ = function(e) {
|
||||
this.setEnabled(false);
|
||||
if (this.win) {
|
||||
this.win.close();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.getStyleRules = function() {
|
||||
return goog.debug.FancyWindow.base(this, 'getStyleRules') +
|
||||
'html,body{height:100%;width:100%;margin:0px;padding:0px;' +
|
||||
'background-color:#FFF;overflow:hidden}' +
|
||||
'*{}' +
|
||||
'.logmsg{border-bottom:1px solid #CCC;padding:2px;font:90% monospace}' +
|
||||
'#head{position:absolute;width:100%;font:x-small arial;' +
|
||||
'border-bottom:2px solid #999;background-color:#EEE;}' +
|
||||
'#head p{margin:0px 5px;}' +
|
||||
'#log{position:absolute;width:100%;background-color:#FFF;}' +
|
||||
'#options{position:absolute;right:0px;width:50%;height:100%;' +
|
||||
'border-left:1px solid #999;background-color:#DDD;display:none;' +
|
||||
'padding-left: 5px;font:normal small arial;overflow:auto;}' +
|
||||
'#openbutton,#closebutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:5px;font:x-small arial;}' +
|
||||
'#clearbutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:80px;font:x-small arial;}' +
|
||||
'#exitbutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:50px;font:x-small arial;}' +
|
||||
'select{font:x-small arial;margin-right:10px;}' +
|
||||
'hr{border:0;height:5px;background-color:#8c8;color:#8c8;}';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the default HTML for the debug window
|
||||
* @return {string} Html.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.getHtml_ = function() {
|
||||
return '' +
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"' +
|
||||
'"http://www.w3.org/TR/html4/loose.dtd">' +
|
||||
'<html><head><title>Logging: ' + this.identifier + '</title>' +
|
||||
'<style>' + this.getStyleRules() + '</style>' +
|
||||
'</head><body>' +
|
||||
'<div id="log" style="overflow:auto"></div>' +
|
||||
'<div id="head">' +
|
||||
'<p><b>Logging: ' + this.identifier + '</b></p><p>' +
|
||||
this.welcomeMessage + '</p>' +
|
||||
'<span id="clearbutton">clear</span>' +
|
||||
'<span id="exitbutton">exit</span>' +
|
||||
'<span id="openbutton">options</span>' +
|
||||
'</div>' +
|
||||
'<div id="options">' +
|
||||
'<big><b>Options:</b></big>' +
|
||||
'<div id="optionsarea"></div>' +
|
||||
'<span id="closebutton">save and close</span>' +
|
||||
'</div>' +
|
||||
'</body></html>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write logger levels to localStorage if possible.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.writeOptionsToLocalStorage_ = function() {
|
||||
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
|
||||
return;
|
||||
}
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var key = goog.debug.FancyWindow.LOCAL_STORE_PREFIX + loggers[i].getName();
|
||||
var level = loggers[i].getLevel();
|
||||
if (key in storedKeys) {
|
||||
if (!level) {
|
||||
window.localStorage.removeItem(key);
|
||||
} else if (window.localStorage.getItem(key) != level.name) {
|
||||
window.localStorage.setItem(key, level.name);
|
||||
}
|
||||
} else if (level) {
|
||||
window.localStorage.setItem(key, level.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sync logger levels with any values stored in localStorage.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.readOptionsFromLocalStorage_ = function() {
|
||||
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
|
||||
return;
|
||||
}
|
||||
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
|
||||
for (var key in storedKeys) {
|
||||
var loggerName = key.replace(goog.debug.FancyWindow.LOCAL_STORE_PREFIX, '');
|
||||
var logger = goog.debug.LogManager.getLogger(loggerName);
|
||||
var curLevel = logger.getLevel();
|
||||
var storedLevel = window.localStorage.getItem(key).toString();
|
||||
if (!curLevel || curLevel.toString() != storedLevel) {
|
||||
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(storedLevel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to create a list of locally stored keys. Used to avoid
|
||||
* expensive localStorage.getItem() calls.
|
||||
* @return {!Object} List of keys.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.getStoredKeys_ = function() {
|
||||
var storedKeys = {};
|
||||
for (var i = 0, len = window.localStorage.length; i < len; i++) {
|
||||
var key = window.localStorage.key(i);
|
||||
if (key != null && goog.string.startsWith(
|
||||
key, goog.debug.FancyWindow.LOCAL_STORE_PREFIX)) {
|
||||
storedKeys[key] = true;
|
||||
}
|
||||
}
|
||||
return storedKeys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a sorted array of all the loggers registered.
|
||||
* @return {!Array<!goog.debug.Logger>} Array of logger instances.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.getLoggers_ = function() {
|
||||
var loggers = goog.object.getValues(goog.debug.LogManager.getLoggers());
|
||||
|
||||
/**
|
||||
* @param {!goog.debug.Logger} a
|
||||
* @param {!goog.debug.Logger} b
|
||||
* @return {number}
|
||||
*/
|
||||
var loggerSort = function(a, b) {
|
||||
return goog.array.defaultCompare(a.getName(), b.getName());
|
||||
};
|
||||
goog.array.sort(loggers, loggerSort);
|
||||
return loggers;
|
||||
};
|
||||
@@ -0,0 +1,362 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of various formatters for logging. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Formatter');
|
||||
goog.provide('goog.debug.HtmlFormatter');
|
||||
goog.provide('goog.debug.TextFormatter');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.RelativeTimeProvider');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for Formatters. A Formatter is used to format a LogRecord into
|
||||
* something that can be displayed to the user.
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.Formatter = function(opt_prefix) {
|
||||
this.prefix_ = opt_prefix || '';
|
||||
|
||||
/**
|
||||
* A provider that returns the relative start time.
|
||||
* @type {goog.debug.RelativeTimeProvider}
|
||||
* @private
|
||||
*/
|
||||
this.startTimeProvider_ =
|
||||
goog.debug.RelativeTimeProvider.getDefaultInstance();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether to append newlines to the end of formatted log records.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.appendNewline = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show absolute time in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showAbsoluteTime = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show relative time in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showRelativeTime = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger name in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showLoggerName = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger exception text.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showExceptionText = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the severity level.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showSeverityLevel = false;
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record.
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the start time provider. By default, this is the default instance
|
||||
* but can be changed.
|
||||
* @param {goog.debug.RelativeTimeProvider} provider The provider to use.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
|
||||
this.startTimeProvider_ = provider;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the start time provider. By default, this is the default instance
|
||||
* but can be changed.
|
||||
* @return {goog.debug.RelativeTimeProvider} The start time provider.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.getStartTimeProvider = function() {
|
||||
return this.startTimeProvider_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the start relative time.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
|
||||
this.startTimeProvider_.reset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string for the time/date of the LogRecord.
|
||||
* @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
|
||||
* @return {string} A string representation of the time/date of the LogRecord.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
|
||||
var time = new Date(logRecord.getMillis());
|
||||
return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
|
||||
goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
|
||||
goog.debug.Formatter.getTwoDigitString_(
|
||||
Math.floor(time.getMilliseconds() / 10));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number as a two-digit string, meaning it prepends a 0 if the
|
||||
* number if less than 10.
|
||||
* @param {number} n The number to format.
|
||||
* @return {string} A two-digit string representation of {@code n}.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getTwoDigitString_ = function(n) {
|
||||
if (n < 10) {
|
||||
return '0' + n;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string for the number of seconds relative to the start time.
|
||||
* Prepads with spaces so that anything less than 1000 seconds takes up the
|
||||
* same number of characters for better formatting.
|
||||
* @param {goog.debug.LogRecord} logRecord The log to compare time to.
|
||||
* @param {number} relativeTimeStart The start time to compare to.
|
||||
* @return {string} The number of seconds of the LogRecord relative to the
|
||||
* start time.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getRelativeTime_ = function(logRecord,
|
||||
relativeTimeStart) {
|
||||
var ms = logRecord.getMillis() - relativeTimeStart;
|
||||
var sec = ms / 1000;
|
||||
var str = sec.toFixed(3);
|
||||
|
||||
var spacesToPrepend = 0;
|
||||
if (sec < 1) {
|
||||
spacesToPrepend = 2;
|
||||
} else {
|
||||
while (sec < 100) {
|
||||
spacesToPrepend++;
|
||||
sec *= 10;
|
||||
}
|
||||
}
|
||||
while (spacesToPrepend-- > 0) {
|
||||
str = ' ' + str;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatter that returns formatted html. See formatRecord for the classes
|
||||
* it uses for various types of formatted output.
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Formatter}
|
||||
*/
|
||||
goog.debug.HtmlFormatter = function(opt_prefix) {
|
||||
goog.debug.Formatter.call(this, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger exception text
|
||||
* @type {boolean}
|
||||
* @override
|
||||
*/
|
||||
goog.debug.HtmlFormatter.prototype.showExceptionText = true;
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string as html.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
|
||||
if (!logRecord) {
|
||||
return '';
|
||||
}
|
||||
// OK not to use goog.html.SafeHtml.unwrap() here.
|
||||
return this.formatRecordAsHtml(logRecord).getTypedStringValue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record.
|
||||
* @param {!goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
|
||||
*/
|
||||
goog.debug.HtmlFormatter.prototype.formatRecordAsHtml = function(logRecord) {
|
||||
var className;
|
||||
switch (logRecord.getLevel().value) {
|
||||
case goog.debug.Logger.Level.SHOUT.value:
|
||||
className = 'dbg-sh';
|
||||
break;
|
||||
case goog.debug.Logger.Level.SEVERE.value:
|
||||
className = 'dbg-sev';
|
||||
break;
|
||||
case goog.debug.Logger.Level.WARNING.value:
|
||||
className = 'dbg-w';
|
||||
break;
|
||||
case goog.debug.Logger.Level.INFO.value:
|
||||
className = 'dbg-i';
|
||||
break;
|
||||
case goog.debug.Logger.Level.FINE.value:
|
||||
default:
|
||||
className = 'dbg-f';
|
||||
break;
|
||||
}
|
||||
|
||||
// HTML for user defined prefix, time, logger name, and severity.
|
||||
var sb = [];
|
||||
sb.push(this.prefix_, ' ');
|
||||
if (this.showAbsoluteTime) {
|
||||
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
|
||||
}
|
||||
if (this.showRelativeTime) {
|
||||
sb.push('[',
|
||||
goog.debug.Formatter.getRelativeTime_(
|
||||
logRecord, this.startTimeProvider_.get()),
|
||||
's] ');
|
||||
}
|
||||
if (this.showLoggerName) {
|
||||
sb.push('[', logRecord.getLoggerName(), '] ');
|
||||
}
|
||||
if (this.showSeverityLevel) {
|
||||
sb.push('[', logRecord.getLevel().name, '] ');
|
||||
}
|
||||
var fullPrefixHtml =
|
||||
goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(sb.join(''));
|
||||
|
||||
// HTML for exception text and log record.
|
||||
var exceptionHtml = goog.html.SafeHtml.EMPTY;
|
||||
if (this.showExceptionText && logRecord.getException()) {
|
||||
exceptionHtml = goog.html.SafeHtml.concat(
|
||||
goog.html.SafeHtml.create('br'),
|
||||
goog.debug.exposeExceptionAsHtml(logRecord.getException()));
|
||||
}
|
||||
var logRecordHtml = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
|
||||
logRecord.getMessage());
|
||||
var recordAndExceptionHtml = goog.html.SafeHtml.create(
|
||||
'span',
|
||||
{'class': className},
|
||||
goog.html.SafeHtml.concat(logRecordHtml, exceptionHtml));
|
||||
|
||||
|
||||
// Combine both pieces of HTML and, if needed, append a final newline.
|
||||
var html;
|
||||
if (this.appendNewline) {
|
||||
html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml,
|
||||
goog.html.SafeHtml.create('br'));
|
||||
} else {
|
||||
html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml);
|
||||
}
|
||||
return html;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatter that returns formatted plain text
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Formatter}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.TextFormatter = function(opt_prefix) {
|
||||
goog.debug.Formatter.call(this, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record as text
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
|
||||
// Build message html
|
||||
var sb = [];
|
||||
sb.push(this.prefix_, ' ');
|
||||
if (this.showAbsoluteTime) {
|
||||
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
|
||||
}
|
||||
if (this.showRelativeTime) {
|
||||
sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
|
||||
this.startTimeProvider_.get()), 's] ');
|
||||
}
|
||||
|
||||
if (this.showLoggerName) {
|
||||
sb.push('[', logRecord.getLoggerName(), '] ');
|
||||
}
|
||||
if (this.showSeverityLevel) {
|
||||
sb.push('[', logRecord.getLevel().name, '] ');
|
||||
}
|
||||
sb.push(logRecord.getMessage());
|
||||
if (this.showExceptionText) {
|
||||
var exception = logRecord.getException();
|
||||
if (exception) {
|
||||
var exceptionText = exception instanceof Error ?
|
||||
exception.message :
|
||||
exception.toString();
|
||||
sb.push('\n', exceptionText);
|
||||
}
|
||||
}
|
||||
if (this.appendNewline) {
|
||||
sb.push('\n');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.Formatter
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.FormatterTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2014 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.FormatterTest');
|
||||
goog.setTestOnly('goog.debug.FormatterTest');
|
||||
|
||||
goog.require('goog.debug.HtmlFormatter');
|
||||
goog.require('goog.debug.LogRecord');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
var EXPECTED_RECORD_HTML_RE =
|
||||
'^prefix \\[.*?\\] \\[  .*?s\\] \\[loggerName\\] ' +
|
||||
'<span class="dbg-f">mess<br>  age<br>Message:';
|
||||
var logRecord;
|
||||
|
||||
|
||||
function setUp() {
|
||||
logRecord = new goog.debug.LogRecord(
|
||||
goog.debug.Logger.Level.CONFIG, 'mess\n age', 'loggerName', 1, 100);
|
||||
// Exception needs to be present for exception text to get printed
|
||||
// by HtmlFormatter.
|
||||
logRecord.setException(new Error('exc\n eption'));
|
||||
}
|
||||
|
||||
|
||||
function testHtmlFormatter_formatRecord() {
|
||||
var formatter = new goog.debug.HtmlFormatter('prefix');
|
||||
var actual = formatter.formatRecord(logRecord);
|
||||
assertRegExp(EXPECTED_RECORD_HTML_RE, actual);
|
||||
}
|
||||
|
||||
|
||||
function testHtmlFormatter_formatRecordAsHtml() {
|
||||
var formatter = new goog.debug.HtmlFormatter('prefix');
|
||||
var actual = formatter.formatRecordAsHtml(logRecord);
|
||||
assertRegExp(EXPECTED_RECORD_HTML_RE, goog.html.SafeHtml.unwrap(actual));
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2011 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Displays frames per second (FPS) for the current window.
|
||||
* Only supported in browsers that support requestAnimationFrame.
|
||||
* See: https://developer.mozilla.org/en/DOM/window.requestAnimationFrame.
|
||||
*
|
||||
* @see ../demos/fpsdisplay.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.FpsDisplay');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.AnimationDelay');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.ui.Component');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Displays frames per seconds that the window this component is
|
||||
* rendered in is animating at.
|
||||
*
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper An optional dom helper.
|
||||
* @constructor
|
||||
* @extends {goog.ui.Component}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.FpsDisplay = function(opt_domHelper) {
|
||||
goog.debug.FpsDisplay.base(this, 'constructor', opt_domHelper);
|
||||
};
|
||||
goog.inherits(goog.debug.FpsDisplay, goog.ui.Component);
|
||||
|
||||
|
||||
/**
|
||||
* CSS class for the FPS display.
|
||||
*/
|
||||
goog.debug.FpsDisplay.CSS = goog.getCssName('goog-fps-display');
|
||||
|
||||
|
||||
/**
|
||||
* The number of samples per FPS report.
|
||||
*/
|
||||
goog.debug.FpsDisplay.SAMPLES = 10;
|
||||
|
||||
|
||||
/**
|
||||
* The current animation.
|
||||
* @type {goog.debug.FpsDisplay.FpsAnimation_}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.animation_ = null;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.createDom = function() {
|
||||
this.setElementInternal(
|
||||
this.getDomHelper().createDom('div', goog.debug.FpsDisplay.CSS));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.enterDocument = function() {
|
||||
goog.debug.FpsDisplay.base(this, 'enterDocument');
|
||||
this.animation_ = new goog.debug.FpsDisplay.FpsAnimation_(this.getElement());
|
||||
this.delay_ = new goog.async.AnimationDelay(
|
||||
this.handleDelay_, this.getDomHelper().getWindow(), this);
|
||||
this.delay_.start();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} now The current time.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.handleDelay_ = function(now) {
|
||||
if (this.isInDocument()) {
|
||||
this.animation_.onAnimationFrame(now);
|
||||
this.delay_.start();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.exitDocument = function() {
|
||||
goog.debug.FpsDisplay.base(this, 'exitDocument');
|
||||
this.animation_ = null;
|
||||
goog.dispose(this.delay_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The average frames per second.
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.getFps = function() {
|
||||
goog.asserts.assert(
|
||||
this.isInDocument(), 'Render the FPS display before querying FPS');
|
||||
return this.animation_.lastFps_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {Element} elem An element to hold the FPS count.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_ = function(elem) {
|
||||
/**
|
||||
* An element to hold the current FPS rate.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.element_ = elem;
|
||||
|
||||
/**
|
||||
* The number of frames observed so far.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.frameNumber_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The last time which we reported FPS at.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastTime_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The last average FPS.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastFps_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} now The current time.
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.onAnimationFrame = function(now) {
|
||||
var SAMPLES = goog.debug.FpsDisplay.SAMPLES;
|
||||
if (this.frameNumber_ % SAMPLES == 0) {
|
||||
this.lastFps_ = Math.round((1000 * SAMPLES) / (now - this.lastTime_));
|
||||
goog.dom.setTextContent(this.element_, this.lastFps_);
|
||||
this.lastTime_ = now;
|
||||
}
|
||||
this.frameNumber_++;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.FpsDisplay
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.FpsDisplayTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2011 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.FpsDisplayTest');
|
||||
goog.setTestOnly('goog.debug.FpsDisplayTest');
|
||||
|
||||
goog.require('goog.debug.FpsDisplay');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var fpsDisplay;
|
||||
var timer;
|
||||
asyncTestCase.stepTimeout = 10 * 1000;
|
||||
|
||||
function setUp() {
|
||||
fpsDisplay = new goog.debug.FpsDisplay();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(fpsDisplay);
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
|
||||
function testRendering() {
|
||||
fpsDisplay.render();
|
||||
|
||||
var elem = fpsDisplay.getElement();
|
||||
assertHTMLEquals('', elem.innerHTML);
|
||||
|
||||
asyncTestCase.waitForAsync('Waiting for some frames to pass');
|
||||
timer = window.setTimeout(function() {
|
||||
var fps = parseInt(elem.innerHTML, 10);
|
||||
assertTrue('FPS of ' + fps + ' should be non-negative', fps >= 0);
|
||||
assertTrue('FPS of ' + fps + ' too big', fps < 1000);
|
||||
asyncTestCase.continueTesting();
|
||||
}, 2000);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2007 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the GcDiagnostics class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.GcDiagnostics');
|
||||
|
||||
goog.require('goog.debug.Trace');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class used for singleton goog.debug.GcDiagnostics. Used to hook into
|
||||
* the L2 ActiveX controller to profile garbage collection information in IE.
|
||||
* Can be used in combination with tracers (goog.debug.Trace), to provide object
|
||||
* allocation counts from within the tracers or used alone by invoking start and
|
||||
* stop.
|
||||
*
|
||||
* See http://go/l2binary for the install.
|
||||
* TODO(user): Move the L2 installer somewhere more general.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.GcDiagnostics_ = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Install the GcDiagnostics tool.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.install = function() {
|
||||
if (goog.userAgent.IE) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var l2Helper = new ActiveXObject('L2.NativeHelper');
|
||||
|
||||
// If using tracers, use the higher precision timer provided by L2.
|
||||
if (goog.debug.Trace_) {
|
||||
goog.debug.Trace_.now = function() {
|
||||
return l2Helper['getMilliSeconds']();
|
||||
};
|
||||
}
|
||||
|
||||
if (l2Helper['gcTracer']) {
|
||||
l2Helper['gcTracer']['installGcTracing']();
|
||||
this.gcTracer_ = l2Helper['gcTracer'];
|
||||
if (goog.debug.Trace) {
|
||||
// If tracers are in use, register the gcTracer so that per tracer
|
||||
// allocations are recorded.
|
||||
goog.debug.Trace.setGcTracer(this.gcTracer_);
|
||||
}
|
||||
}
|
||||
goog.log.info(this.logger_, 'Installed L2 native helper');
|
||||
} catch (e) {
|
||||
goog.log.info(this.logger_, 'Failed to install L2 native helper: ' + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logger for the gcDiagnotics
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.logger_ =
|
||||
goog.log.getLogger('goog.debug.GcDiagnostics');
|
||||
|
||||
|
||||
/**
|
||||
* Starts recording garbage collection information. If a trace is already in
|
||||
* progress, it is ended.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.start = function() {
|
||||
if (this.gcTracer_) {
|
||||
if (this.gcTracer_['isTracing']()) {
|
||||
this.gcTracer_['endGcTracing']();
|
||||
}
|
||||
this.gcTracer_['startGcTracing']();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops recording garbage collection information. Logs details on the garbage
|
||||
* collections that occurred between start and stop. If tracers are in use,
|
||||
* adds comments where each GC occurs.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.stop = function() {
|
||||
if (this.gcTracer_ && this.gcTracer_['isTracing']()) {
|
||||
var gcTracer = this.gcTracer_;
|
||||
this.gcTracer_['endGcTracing']();
|
||||
|
||||
var numGCs = gcTracer['getNumTraces']();
|
||||
goog.log.info(this.logger_, '*********GC TRACE*********');
|
||||
goog.log.info(this.logger_, 'GC ran ' + numGCs + ' times.');
|
||||
var totalTime = 0;
|
||||
for (var i = 0; i < numGCs; i++) {
|
||||
var trace = gcTracer['getTrace'](i);
|
||||
|
||||
var msStart = trace['gcStartTime'];
|
||||
var msElapsed = trace['gcElapsedTime'];
|
||||
|
||||
var msRounded = Math.round(msElapsed * 10) / 10;
|
||||
var s = 'GC ' + i + ': ' + msRounded + ' ms, ' +
|
||||
'numVValAlloc=' + trace['numVValAlloc'] + ', ' +
|
||||
'numVarAlloc=' + trace['numVarAlloc'] + ', ' +
|
||||
'numBytesSysAlloc=' + trace['numBytesSysAlloc'];
|
||||
if (goog.debug.Trace) {
|
||||
goog.debug.Trace.addComment(s, null, msStart);
|
||||
}
|
||||
goog.log.info(this.logger_, s);
|
||||
totalTime += msElapsed;
|
||||
}
|
||||
if (goog.debug.Trace) {
|
||||
goog.debug.Trace.addComment('Total GC time was ' + totalTime + ' ms.');
|
||||
}
|
||||
goog.log.info(this.logger_, 'Total GC time was ' + totalTime + ' ms.');
|
||||
goog.log.info(this.logger_, '*********GC TRACE*********');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton GcDiagnostics object
|
||||
* @type {goog.debug.GcDiagnostics_}
|
||||
*/
|
||||
goog.debug.GcDiagnostics = new goog.debug.GcDiagnostics_();
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2010 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview A buffer for log records. The purpose of this is to improve
|
||||
* logging performance by re-using old objects when the buffer becomes full and
|
||||
* to eliminate the need for each app to implement their own log buffer. The
|
||||
* disadvantage to doing this is that log handlers cannot maintain references to
|
||||
* log records and expect that they are not overwriten at a later point.
|
||||
*
|
||||
* @author agrieve@google.com (Andrew Grieve)
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.LogBuffer');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.LogRecord');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates the log buffer.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.debug.LogBuffer = function() {
|
||||
goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
|
||||
'Cannot use goog.debug.LogBuffer without defining ' +
|
||||
'goog.debug.LogBuffer.CAPACITY.');
|
||||
this.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A static method that always returns the same instance of LogBuffer.
|
||||
* @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
|
||||
*/
|
||||
goog.debug.LogBuffer.getInstance = function() {
|
||||
if (!goog.debug.LogBuffer.instance_) {
|
||||
// This function is written with the return statement after the assignment
|
||||
// to avoid the jscompiler StripCode bug described in http://b/2608064.
|
||||
// After that bug is fixed this can be refactored.
|
||||
goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
|
||||
}
|
||||
return goog.debug.LogBuffer.instance_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {number} The number of log records to buffer. 0 means disable
|
||||
* buffering.
|
||||
*/
|
||||
goog.define('goog.debug.LogBuffer.CAPACITY', 0);
|
||||
|
||||
|
||||
/**
|
||||
* The array to store the records.
|
||||
* @type {!Array<!goog.debug.LogRecord|undefined>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.buffer_;
|
||||
|
||||
|
||||
/**
|
||||
* The index of the most recently added record or -1 if there are no records.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.curIndex_;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the buffer is at capacity.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.isFull_;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record to the buffer, possibly overwriting the oldest record.
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {string} msg The string message.
|
||||
* @param {string} loggerName The name of the source logger.
|
||||
* @return {!goog.debug.LogRecord} The log record.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
|
||||
var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
|
||||
this.curIndex_ = curIndex;
|
||||
if (this.isFull_) {
|
||||
var ret = this.buffer_[curIndex];
|
||||
ret.reset(level, msg, loggerName);
|
||||
return ret;
|
||||
}
|
||||
this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
|
||||
return this.buffer_[curIndex] =
|
||||
new goog.debug.LogRecord(level, msg, loggerName);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the log buffer is enabled.
|
||||
*/
|
||||
goog.debug.LogBuffer.isBufferingEnabled = function() {
|
||||
return goog.debug.LogBuffer.CAPACITY > 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all buffered log records.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.clear = function() {
|
||||
this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
|
||||
this.curIndex_ = -1;
|
||||
this.isFull_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the given function for each buffered log record, starting with the
|
||||
* oldest one.
|
||||
* @param {function(!goog.debug.LogRecord)} func The function to call.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
|
||||
var buffer = this.buffer_;
|
||||
// Corner case: no records.
|
||||
if (!buffer[0]) {
|
||||
return;
|
||||
}
|
||||
var curIndex = this.curIndex_;
|
||||
var i = this.isFull_ ? curIndex : -1;
|
||||
do {
|
||||
i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
|
||||
func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
|
||||
} while (i != curIndex);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.LogBuffer
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.LogBufferTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2010 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.LogBufferTest');
|
||||
goog.setTestOnly('goog.debug.LogBufferTest');
|
||||
|
||||
goog.require('goog.debug.LogBuffer');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var DUMMY_LEVELS = [
|
||||
goog.debug.Logger.Level.INFO,
|
||||
goog.debug.Logger.Level.WARNING,
|
||||
goog.debug.Logger.Level.SEVERE];
|
||||
var DUMMY_MESSAGES = ['a', 'b', 'c'];
|
||||
var DUMMY_NAMES = ['X', 'Y', 'Z'];
|
||||
|
||||
var buffer;
|
||||
var dummyIndex = 0;
|
||||
|
||||
function setUp() {
|
||||
goog.debug.LogBuffer.CAPACITY = 4;
|
||||
goog.debug.LogBuffer.instance_ = null;
|
||||
buffer = goog.debug.LogBuffer.getInstance();
|
||||
}
|
||||
|
||||
function verifyRecord(expectedIndex, record) {
|
||||
var index = expectedIndex % DUMMY_MESSAGES.length;
|
||||
var message = DUMMY_MESSAGES[index];
|
||||
var level = DUMMY_LEVELS[index];
|
||||
var name = DUMMY_NAMES[index];
|
||||
assertEquals('Wrong level for record ' + expectedIndex, level,
|
||||
record.getLevel());
|
||||
assertEquals('Wrong message for record ' + expectedIndex, message,
|
||||
record.getMessage());
|
||||
assertEquals('Wrong name for record ' + expectedIndex, name,
|
||||
record.getLoggerName());
|
||||
}
|
||||
|
||||
function addAndVerifyRecord() {
|
||||
var index = dummyIndex % DUMMY_MESSAGES.length;
|
||||
var level = DUMMY_LEVELS[index];
|
||||
var message = DUMMY_MESSAGES[index];
|
||||
var name = DUMMY_NAMES[index];
|
||||
var record = buffer.addRecord(level, message, name);
|
||||
verifyRecord(dummyIndex, record);
|
||||
dummyIndex++;
|
||||
}
|
||||
|
||||
function addSomeRecords(howMany) {
|
||||
for (var i = 0; i < howMany; i++) {
|
||||
addAndVerifyRecord();
|
||||
}
|
||||
}
|
||||
|
||||
function testAddRecord() {
|
||||
addSomeRecords(goog.debug.LogBuffer.CAPACITY * 3);
|
||||
}
|
||||
|
||||
function testIsFull() {
|
||||
assertFalse('Should not be full.', buffer.isFull_);
|
||||
addSomeRecords(goog.debug.LogBuffer.CAPACITY * 1.5);
|
||||
assertTrue('Should be full.', buffer.isFull_);
|
||||
buffer.clear();
|
||||
assertFalse('Should not be full after clear().', buffer.isFull_);
|
||||
addSomeRecords(goog.debug.LogBuffer.CAPACITY - 1);
|
||||
assertFalse('Should not be full but almost full.', buffer.isFull_);
|
||||
}
|
||||
|
||||
function testForEachRecord() {
|
||||
// Test with it half full.
|
||||
var howMany1 = goog.debug.LogBuffer.CAPACITY / 2;
|
||||
addSomeRecords(howMany1);
|
||||
var counter1 = 0;
|
||||
buffer.forEachRecord(function(record) {
|
||||
verifyRecord(counter1++, record);
|
||||
});
|
||||
assertEquals('Wrong number of records when half full.', howMany1, counter1);
|
||||
|
||||
// Test with it full.
|
||||
var howMany2 = goog.debug.LogBuffer.CAPACITY;
|
||||
addSomeRecords(howMany2);
|
||||
var index = counter1;
|
||||
buffer.forEachRecord(function(record) {
|
||||
verifyRecord(index++, record);
|
||||
});
|
||||
assertEquals('Wrong number of records when full.', howMany1 + howMany2,
|
||||
index);
|
||||
}
|
||||
@@ -0,0 +1,873 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Logger class. Please minimize dependencies
|
||||
* this file has on other closure classes as any dependency it takes won't be
|
||||
* able to use the logging infrastructure.
|
||||
*
|
||||
* @see ../demos/debug.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.LogManager');
|
||||
goog.provide('goog.debug.Loggable');
|
||||
goog.provide('goog.debug.Logger');
|
||||
goog.provide('goog.debug.Logger.Level');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.LogBuffer');
|
||||
goog.require('goog.debug.LogRecord');
|
||||
|
||||
|
||||
/**
|
||||
* A message value that can be handled by a Logger.
|
||||
*
|
||||
* Functions are treated like callbacks, but are only called when the event's
|
||||
* log level is enabled. This is useful for logging messages that are expensive
|
||||
* to construct.
|
||||
*
|
||||
* @typedef {string|function(): string}
|
||||
*/
|
||||
goog.debug.Loggable;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Logger is an object used for logging debug messages. Loggers are
|
||||
* normally named, using a hierarchical dot-separated namespace. Logger names
|
||||
* can be arbitrary strings, but they should normally be based on the package
|
||||
* name or class name of the logged component, such as goog.net.BrowserChannel.
|
||||
*
|
||||
* The Logger object is loosely based on the java class
|
||||
* java.util.logging.Logger. It supports different levels of filtering for
|
||||
* different loggers.
|
||||
*
|
||||
* The logger object should never be instantiated by application code. It
|
||||
* should always use the goog.debug.Logger.getLogger function.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} name The name of the Logger.
|
||||
* @final
|
||||
*/
|
||||
goog.debug.Logger = function(name) {
|
||||
/**
|
||||
* Name of the Logger. Generally a dot-separated namespace
|
||||
* @private {string}
|
||||
*/
|
||||
this.name_ = name;
|
||||
|
||||
/**
|
||||
* Parent Logger.
|
||||
* @private {goog.debug.Logger}
|
||||
*/
|
||||
this.parent_ = null;
|
||||
|
||||
/**
|
||||
* Level that this logger only filters above. Null indicates it should
|
||||
* inherit from the parent.
|
||||
* @private {goog.debug.Logger.Level}
|
||||
*/
|
||||
this.level_ = null;
|
||||
|
||||
/**
|
||||
* Map of children loggers. The keys are the leaf names of the children and
|
||||
* the values are the child loggers.
|
||||
* @private {Object}
|
||||
*/
|
||||
this.children_ = null;
|
||||
|
||||
/**
|
||||
* Handlers that are listening to this logger.
|
||||
* @private {Array<Function>}
|
||||
*/
|
||||
this.handlers_ = null;
|
||||
};
|
||||
|
||||
|
||||
/** @const */
|
||||
goog.debug.Logger.ROOT_LOGGER_NAME = '';
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Toggles whether loggers other than the root logger can have
|
||||
* log handlers attached to them and whether they can have their log level
|
||||
* set. Logging is a bit faster when this is set to false.
|
||||
*/
|
||||
goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
|
||||
|
||||
|
||||
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
/**
|
||||
* @type {!Array<Function>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.rootHandlers_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* @type {goog.debug.Logger.Level}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.rootLevel_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Level class defines a set of standard logging levels that
|
||||
* can be used to control logging output. The logging Level objects
|
||||
* are ordered and are specified by ordered integers. Enabling logging
|
||||
* at a given level also enables logging at all higher levels.
|
||||
* <p>
|
||||
* Clients should normally use the predefined Level constants such
|
||||
* as Level.SEVERE.
|
||||
* <p>
|
||||
* The levels in descending order are:
|
||||
* <ul>
|
||||
* <li>SEVERE (highest value)
|
||||
* <li>WARNING
|
||||
* <li>INFO
|
||||
* <li>CONFIG
|
||||
* <li>FINE
|
||||
* <li>FINER
|
||||
* <li>FINEST (lowest value)
|
||||
* </ul>
|
||||
* In addition there is a level OFF that can be used to turn
|
||||
* off logging, and a level ALL that can be used to enable
|
||||
* logging of all messages.
|
||||
*
|
||||
* @param {string} name The name of the level.
|
||||
* @param {number} value The numeric value of the level.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.debug.Logger.Level = function(name, value) {
|
||||
/**
|
||||
* The name of the level
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = name;
|
||||
|
||||
/**
|
||||
* The numeric value of the level
|
||||
* @type {number}
|
||||
*/
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} String representation of the logger level.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Logger.Level.prototype.toString = function() {
|
||||
return this.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* OFF is a special level that can be used to turn off logging.
|
||||
* This level is initialized to <CODE>Infinity</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.OFF =
|
||||
new goog.debug.Logger.Level('OFF', Infinity);
|
||||
|
||||
|
||||
/**
|
||||
* SHOUT is a message level for extra debugging loudness.
|
||||
* This level is initialized to <CODE>1200</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
|
||||
|
||||
|
||||
/**
|
||||
* SEVERE is a message level indicating a serious failure.
|
||||
* This level is initialized to <CODE>1000</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
|
||||
|
||||
|
||||
/**
|
||||
* WARNING is a message level indicating a potential problem.
|
||||
* This level is initialized to <CODE>900</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
|
||||
|
||||
|
||||
/**
|
||||
* INFO is a message level for informational messages.
|
||||
* This level is initialized to <CODE>800</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
|
||||
|
||||
|
||||
/**
|
||||
* CONFIG is a message level for static configuration messages.
|
||||
* This level is initialized to <CODE>700</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
|
||||
|
||||
|
||||
/**
|
||||
* FINE is a message level providing tracing information.
|
||||
* This level is initialized to <CODE>500</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
|
||||
|
||||
|
||||
/**
|
||||
* FINER indicates a fairly detailed tracing message.
|
||||
* This level is initialized to <CODE>400</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
|
||||
|
||||
/**
|
||||
* FINEST indicates a highly detailed tracing message.
|
||||
* This level is initialized to <CODE>300</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
|
||||
goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
|
||||
|
||||
|
||||
/**
|
||||
* ALL indicates that all messages should be logged.
|
||||
* This level is initialized to <CODE>0</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
|
||||
|
||||
|
||||
/**
|
||||
* The predefined levels.
|
||||
* @type {!Array<!goog.debug.Logger.Level>}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.Logger.Level.PREDEFINED_LEVELS = [
|
||||
goog.debug.Logger.Level.OFF,
|
||||
goog.debug.Logger.Level.SHOUT,
|
||||
goog.debug.Logger.Level.SEVERE,
|
||||
goog.debug.Logger.Level.WARNING,
|
||||
goog.debug.Logger.Level.INFO,
|
||||
goog.debug.Logger.Level.CONFIG,
|
||||
goog.debug.Logger.Level.FINE,
|
||||
goog.debug.Logger.Level.FINER,
|
||||
goog.debug.Logger.Level.FINEST,
|
||||
goog.debug.Logger.Level.ALL];
|
||||
|
||||
|
||||
/**
|
||||
* A lookup map used to find the level object based on the name or value of
|
||||
* the level object.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the predefined levels cache and populates it.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_ = {};
|
||||
for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
|
||||
i++) {
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the predefined level with the given name.
|
||||
* @param {string} name The name of the level.
|
||||
* @return {goog.debug.Logger.Level} The level, or null if none found.
|
||||
*/
|
||||
goog.debug.Logger.Level.getPredefinedLevel = function(name) {
|
||||
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_();
|
||||
}
|
||||
|
||||
return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the highest predefined level <= #value.
|
||||
* @param {number} value Level value.
|
||||
* @return {goog.debug.Logger.Level} The level, or null if none found.
|
||||
*/
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
|
||||
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_();
|
||||
}
|
||||
|
||||
if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
return goog.debug.Logger.Level.predefinedLevelsCache_[value];
|
||||
}
|
||||
|
||||
for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
|
||||
var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
|
||||
if (level.value <= value) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds or creates a logger for a named subsystem. If a logger has already been
|
||||
* created with the given name it is returned. Otherwise a new logger is
|
||||
* created. If a new logger is created its log level will be configured based
|
||||
* on the LogManager configuration and it will configured to also send logging
|
||||
* output to its parent's handlers. It will be registered in the LogManager
|
||||
* global namespace.
|
||||
*
|
||||
* @param {string} name A name for the logger. This should be a dot-separated
|
||||
* name and should normally be based on the package name or class name of the
|
||||
* subsystem, such as goog.net.BrowserChannel.
|
||||
* @return {!goog.debug.Logger} The named logger.
|
||||
* @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
|
||||
*/
|
||||
goog.debug.Logger.getLogger = function(name) {
|
||||
return goog.debug.LogManager.getLogger(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message to profiling tools, if available.
|
||||
* {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
|
||||
* {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
|
||||
* @param {string} msg The message to log.
|
||||
*/
|
||||
goog.debug.Logger.logToProfilers = function(msg) {
|
||||
// Using goog.global, as loggers might be used in window-less contexts.
|
||||
if (goog.global['console']) {
|
||||
if (goog.global['console']['timeStamp']) {
|
||||
// Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
|
||||
goog.global['console']['timeStamp'](msg);
|
||||
} else if (goog.global['console']['markTimeline']) {
|
||||
// TODO(user): markTimeline is deprecated. Drop this else clause entirely
|
||||
// after Chrome M14 hits stable.
|
||||
goog.global['console']['markTimeline'](msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (goog.global['msWriteProfilerMark']) {
|
||||
// Logs a message to the Microsoft profiler
|
||||
goog.global['msWriteProfilerMark'](msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the name of this logger.
|
||||
* @return {string} The name of this logger.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler to the logger. This doesn't use the event system because
|
||||
* we want to be able to add logging to the event system.
|
||||
* @param {Function} handler Handler function to add.
|
||||
*/
|
||||
goog.debug.Logger.prototype.addHandler = function(handler) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
if (goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
if (!this.handlers_) {
|
||||
this.handlers_ = [];
|
||||
}
|
||||
this.handlers_.push(handler);
|
||||
} else {
|
||||
goog.asserts.assert(!this.name_,
|
||||
'Cannot call addHandler on a non-root logger when ' +
|
||||
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
|
||||
goog.debug.Logger.rootHandlers_.push(handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a handler from the logger. This doesn't use the event system because
|
||||
* we want to be able to add logging to the event system.
|
||||
* @param {Function} handler Handler function to remove.
|
||||
* @return {boolean} Whether the handler was removed.
|
||||
*/
|
||||
goog.debug.Logger.prototype.removeHandler = function(handler) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
|
||||
goog.debug.Logger.rootHandlers_;
|
||||
return !!handlers && goog.array.remove(handlers, handler);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parent of this logger.
|
||||
* @return {goog.debug.Logger} The parent logger or null if this is the root.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getParent = function() {
|
||||
return this.parent_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the children of this logger as a map of the child name to the logger.
|
||||
* @return {!Object} The map where the keys are the child leaf names and the
|
||||
* values are the Logger objects.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getChildren = function() {
|
||||
if (!this.children_) {
|
||||
this.children_ = {};
|
||||
}
|
||||
return this.children_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the log level specifying which message levels will be logged by this
|
||||
* logger. Message levels lower than this value will be discarded.
|
||||
* The level value Level.OFF can be used to turn off logging. If the new level
|
||||
* is null, it means that this node should inherit its level from its nearest
|
||||
* ancestor with a specific (non-null) level value.
|
||||
*
|
||||
* @param {goog.debug.Logger.Level} level The new level.
|
||||
*/
|
||||
goog.debug.Logger.prototype.setLevel = function(level) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
if (goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
this.level_ = level;
|
||||
} else {
|
||||
goog.asserts.assert(!this.name_,
|
||||
'Cannot call setLevel() on a non-root logger when ' +
|
||||
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
|
||||
goog.debug.Logger.rootLevel_ = level;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the log level specifying which message levels will be logged by this
|
||||
* logger. Message levels lower than this value will be discarded.
|
||||
* The level value Level.OFF can be used to turn off logging. If the level
|
||||
* is null, it means that this node should inherit its level from its nearest
|
||||
* ancestor with a specific (non-null) level value.
|
||||
*
|
||||
* @return {goog.debug.Logger.Level} The level.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getLevel = function() {
|
||||
return goog.debug.LOGGING_ENABLED ?
|
||||
this.level_ : goog.debug.Logger.Level.OFF;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the effective level of the logger based on its ancestors' levels.
|
||||
* @return {goog.debug.Logger.Level} The level.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getEffectiveLevel = function() {
|
||||
if (!goog.debug.LOGGING_ENABLED) {
|
||||
return goog.debug.Logger.Level.OFF;
|
||||
}
|
||||
|
||||
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
return goog.debug.Logger.rootLevel_;
|
||||
}
|
||||
if (this.level_) {
|
||||
return this.level_;
|
||||
}
|
||||
if (this.parent_) {
|
||||
return this.parent_.getEffectiveLevel();
|
||||
}
|
||||
goog.asserts.fail('Root logger has no level set.');
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a message of the given level would actually be logged by this
|
||||
* logger. This check is based on the Loggers effective level, which may be
|
||||
* inherited from its parent.
|
||||
* @param {goog.debug.Logger.Level} level The level to check.
|
||||
* @return {boolean} Whether the message would be logged.
|
||||
*/
|
||||
goog.debug.Logger.prototype.isLoggable = function(level) {
|
||||
return goog.debug.LOGGING_ENABLED &&
|
||||
level.value >= this.getEffectiveLevel().value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message. If the logger is currently enabled for the
|
||||
* given message level then the given message is forwarded to all the
|
||||
* registered output Handler objects.
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error|Object=} opt_exception An exception associated with the
|
||||
* message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
|
||||
// java caches the effective level, not sure it's necessary here
|
||||
if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
|
||||
// Message callbacks can be useful when a log message is expensive to build.
|
||||
if (goog.isFunction(msg)) {
|
||||
msg = msg();
|
||||
}
|
||||
|
||||
this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new log record and adds the exception (if present) to it.
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {string} msg The string message.
|
||||
* @param {Error|Object=} opt_exception An exception associated with the
|
||||
* message.
|
||||
* @return {!goog.debug.LogRecord} A log record.
|
||||
* @suppress {es5Strict}
|
||||
*/
|
||||
goog.debug.Logger.prototype.getLogRecord = function(
|
||||
level, msg, opt_exception) {
|
||||
if (goog.debug.LogBuffer.isBufferingEnabled()) {
|
||||
var logRecord =
|
||||
goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
|
||||
} else {
|
||||
logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
|
||||
}
|
||||
if (opt_exception) {
|
||||
logRecord.setException(opt_exception);
|
||||
}
|
||||
return logRecord;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.SHOUT level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.SEVERE level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.WARNING level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.INFO level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.info = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.CONFIG level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.config = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.FINE level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.FINER level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message at the Logger.Level.FINEST level.
|
||||
* If the logger is currently enabled for the given message level then the
|
||||
* given message is forwarded to all the registered output Handler objects.
|
||||
* @param {goog.debug.Loggable} msg The message to log.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
|
||||
if (goog.debug.LOGGING_ENABLED) {
|
||||
this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a LogRecord. If the logger is currently enabled for the
|
||||
* given message level then the given message is forwarded to all the
|
||||
* registered output Handler objects.
|
||||
* @param {goog.debug.LogRecord} logRecord A log record to log.
|
||||
*/
|
||||
goog.debug.Logger.prototype.logRecord = function(logRecord) {
|
||||
if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
|
||||
this.doLogRecord_(logRecord);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a LogRecord.
|
||||
* @param {goog.debug.LogRecord} logRecord A log record to log.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
|
||||
goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
|
||||
if (goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
var target = this;
|
||||
while (target) {
|
||||
target.callPublish_(logRecord);
|
||||
target = target.getParent();
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
|
||||
handler(logRecord);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the handlers for publish.
|
||||
* @param {goog.debug.LogRecord} logRecord The log record to publish.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
|
||||
if (this.handlers_) {
|
||||
for (var i = 0, handler; handler = this.handlers_[i]; i++) {
|
||||
handler(logRecord);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the parent of this logger. This is used for setting up the logger tree.
|
||||
* @param {goog.debug.Logger} parent The parent logger.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.setParent_ = function(parent) {
|
||||
this.parent_ = parent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a child to this logger. This is used for setting up the logger tree.
|
||||
* @param {string} name The leaf name of the child.
|
||||
* @param {goog.debug.Logger} logger The child logger.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.addChild_ = function(name, logger) {
|
||||
this.getChildren()[name] = logger;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* There is a single global LogManager object that is used to maintain a set of
|
||||
* shared state about Loggers and log services. This is loosely based on the
|
||||
* java class java.util.logging.LogManager.
|
||||
* @const
|
||||
*/
|
||||
goog.debug.LogManager = {};
|
||||
|
||||
|
||||
/**
|
||||
* Map of logger names to logger objects.
|
||||
*
|
||||
* @type {!Object<string, !goog.debug.Logger>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogManager.loggers_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* The root logger which is the root of the logger tree.
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogManager.rootLogger_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the LogManager if not already initialized.
|
||||
*/
|
||||
goog.debug.LogManager.initialize = function() {
|
||||
if (!goog.debug.LogManager.rootLogger_) {
|
||||
goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
|
||||
goog.debug.Logger.ROOT_LOGGER_NAME);
|
||||
goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
|
||||
goog.debug.LogManager.rootLogger_;
|
||||
goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the loggers.
|
||||
* @return {!Object<string, !goog.debug.Logger>} Map of logger names to logger
|
||||
* objects.
|
||||
*/
|
||||
goog.debug.LogManager.getLoggers = function() {
|
||||
return goog.debug.LogManager.loggers_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the root of the logger tree namespace, the logger with the empty
|
||||
* string as its name.
|
||||
*
|
||||
* @return {!goog.debug.Logger} The root logger.
|
||||
*/
|
||||
goog.debug.LogManager.getRoot = function() {
|
||||
goog.debug.LogManager.initialize();
|
||||
return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds a named logger.
|
||||
*
|
||||
* @param {string} name A name for the logger. This should be a dot-separated
|
||||
* name and should normally be based on the package name or class name of the
|
||||
* subsystem, such as goog.net.BrowserChannel.
|
||||
* @return {!goog.debug.Logger} The named logger.
|
||||
*/
|
||||
goog.debug.LogManager.getLogger = function(name) {
|
||||
goog.debug.LogManager.initialize();
|
||||
var ret = goog.debug.LogManager.loggers_[name];
|
||||
return ret || goog.debug.LogManager.createLogger_(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that can be passed to goog.debug.catchErrors. The function
|
||||
* will log all reported errors using the given logger.
|
||||
* @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
|
||||
* Defaults to the root logger.
|
||||
* @return {function(Object)} The created function.
|
||||
*/
|
||||
goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
|
||||
return function(info) {
|
||||
var logger = opt_logger || goog.debug.LogManager.getRoot();
|
||||
logger.severe('Error: ' + info.message + ' (' + info.fileName +
|
||||
' @ Line: ' + info.line + ')');
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the named logger. Will also create the parents of the named logger
|
||||
* if they don't yet exist.
|
||||
* @param {string} name The name of the logger.
|
||||
* @return {!goog.debug.Logger} The named logger.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogManager.createLogger_ = function(name) {
|
||||
// find parent logger
|
||||
var logger = new goog.debug.Logger(name);
|
||||
if (goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
var lastDotIndex = name.lastIndexOf('.');
|
||||
var parentName = name.substr(0, lastDotIndex);
|
||||
var leafName = name.substr(lastDotIndex + 1);
|
||||
var parentLogger = goog.debug.LogManager.getLogger(parentName);
|
||||
|
||||
// tell the parent about the child and the child about the parent
|
||||
parentLogger.addChild_(leafName, logger);
|
||||
logger.setParent_(parentLogger);
|
||||
}
|
||||
|
||||
goog.debug.LogManager.loggers_[name] = logger;
|
||||
return logger;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.Logger
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.LoggerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.LoggerTest');
|
||||
goog.setTestOnly('goog.debug.LoggerTest');
|
||||
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function getDebug(sb, logger, level) {
|
||||
var spacer = '';
|
||||
if (level) {
|
||||
spacer = (new Array(level + 1)).join(' ');
|
||||
}
|
||||
sb[sb.length] = spacer;
|
||||
var name = logger.getName();
|
||||
if (name) {
|
||||
sb[sb.length] = name;
|
||||
} else {
|
||||
sb[sb.length] = 'ROOT';
|
||||
}
|
||||
sb[sb.length] = '\n';
|
||||
var children = logger.getChildren();
|
||||
for (var key in children) {
|
||||
getDebug(sb, children[key], level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testParents() {
|
||||
var l1 = goog.debug.Logger.getLogger('goog.test');
|
||||
var l2 = goog.debug.Logger.getLogger('goog.bar');
|
||||
var l3 = goog.debug.Logger.getLogger('goog.bar.foo');
|
||||
var l4 = goog.debug.Logger.getLogger('goog.bar.baaz');
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
var googLogger = goog.debug.Logger.getLogger('goog');
|
||||
assertEquals(rootLogger, googLogger.getParent());
|
||||
assertEquals(googLogger, l1.getParent());
|
||||
assertEquals(googLogger, l2.getParent());
|
||||
assertEquals(l2, l3.getParent());
|
||||
assertEquals(l2, l4.getParent());
|
||||
}
|
||||
|
||||
function testLogging() {
|
||||
var root = goog.debug.LogManager.getRoot();
|
||||
var handler = new TestHandler();
|
||||
var f = goog.bind(handler.onPublish, handler);
|
||||
root.addHandler(f);
|
||||
var l4 = goog.debug.Logger.getLogger('goog.bar.baaz');
|
||||
l4.log(goog.debug.Logger.Level.WARNING, 'foo');
|
||||
assertNotNull(handler.logRecord);
|
||||
assertEquals(goog.debug.Logger.Level.WARNING, handler.logRecord.getLevel());
|
||||
assertEquals('foo', handler.logRecord.getMessage());
|
||||
handler.logRecord = null;
|
||||
root.removeHandler(f);
|
||||
l4.log(goog.debug.Logger.Level.WARNING, 'foo');
|
||||
assertNull(handler.logRecord);
|
||||
}
|
||||
|
||||
function testFiltering() {
|
||||
var root = goog.debug.LogManager.getRoot();
|
||||
var handler = new TestHandler();
|
||||
var f = goog.bind(handler.onPublish, handler);
|
||||
root.addHandler(f);
|
||||
var l3 = goog.debug.Logger.getLogger('goog.bar.foo');
|
||||
l3.setLevel(goog.debug.Logger.Level.WARNING);
|
||||
var l4 = goog.debug.Logger.getLogger('goog.bar.baaz');
|
||||
l4.setLevel(goog.debug.Logger.Level.INFO);
|
||||
l4.log(goog.debug.Logger.Level.WARNING, 'foo');
|
||||
assertNotNull(handler.logRecord);
|
||||
assertEquals(goog.debug.Logger.Level.WARNING, handler.logRecord.getLevel());
|
||||
assertEquals('foo', handler.logRecord.getMessage());
|
||||
handler.reset();
|
||||
l3.log(goog.debug.Logger.Level.INFO, 'bar');
|
||||
assertNull(handler.logRecord);
|
||||
l3.log(goog.debug.Logger.Level.WARNING, 'baaz');
|
||||
assertNotNull(handler.logRecord);
|
||||
handler.reset();
|
||||
l3.log(goog.debug.Logger.Level.SEVERE, 'baaz');
|
||||
assertNotNull(handler.logRecord);
|
||||
}
|
||||
|
||||
function testException() {
|
||||
var root = goog.debug.LogManager.getRoot();
|
||||
var handler = new TestHandler();
|
||||
var f = goog.bind(handler.onPublish, handler);
|
||||
root.addHandler(f);
|
||||
var logger = goog.debug.Logger.getLogger('goog.debug.logger_test');
|
||||
var ex = Error('boo!');
|
||||
logger.severe('hello', ex);
|
||||
assertNotNull(handler.logRecord);
|
||||
assertEquals(goog.debug.Logger.Level.SEVERE, handler.logRecord.getLevel());
|
||||
assertEquals('hello', handler.logRecord.getMessage());
|
||||
assertEquals(ex, handler.logRecord.getException());
|
||||
assertEquals('boo!', handler.logRecord.getException().message);
|
||||
}
|
||||
|
||||
function testMessageCallbacks() {
|
||||
var root = goog.debug.LogManager.getRoot();
|
||||
var handler = new TestHandler();
|
||||
var f = goog.bind(handler.onPublish, handler);
|
||||
root.addHandler(f);
|
||||
var l3 = goog.debug.Logger.getLogger('goog.bar.foo');
|
||||
l3.setLevel(goog.debug.Logger.Level.WARNING);
|
||||
|
||||
l3.log(goog.debug.Logger.Level.INFO, function() {
|
||||
throw "Message callback shouldn't be called when below logger's level!";
|
||||
});
|
||||
assertNull(handler.logRecord);
|
||||
|
||||
l3.log(goog.debug.Logger.Level.WARNING, function() {return 'heya'});
|
||||
assertNotNull(handler.logRecord);
|
||||
assertEquals(goog.debug.Logger.Level.WARNING, handler.logRecord.getLevel());
|
||||
assertEquals('heya', handler.logRecord.getMessage());
|
||||
}
|
||||
|
||||
function testGetPredefinedLevel() {
|
||||
assertEquals(goog.debug.Logger.Level.OFF,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('OFF'));
|
||||
assertEquals(goog.debug.Logger.Level.SHOUT,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('SHOUT'));
|
||||
assertEquals(goog.debug.Logger.Level.SEVERE,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('SEVERE'));
|
||||
assertEquals(goog.debug.Logger.Level.WARNING,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('WARNING'));
|
||||
assertEquals(goog.debug.Logger.Level.INFO,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('INFO'));
|
||||
assertEquals(goog.debug.Logger.Level.CONFIG,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('CONFIG'));
|
||||
assertEquals(goog.debug.Logger.Level.FINE,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('FINE'));
|
||||
assertEquals(goog.debug.Logger.Level.FINER,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('FINER'));
|
||||
assertEquals(goog.debug.Logger.Level.FINEST,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('FINEST'));
|
||||
assertEquals(goog.debug.Logger.Level.ALL,
|
||||
goog.debug.Logger.Level.getPredefinedLevel('ALL'));
|
||||
}
|
||||
|
||||
function testGetPredefinedLevelByValue() {
|
||||
assertEquals(goog.debug.Logger.Level.OFF,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(Infinity));
|
||||
assertEquals(goog.debug.Logger.Level.SHOUT,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(1300));
|
||||
assertEquals(goog.debug.Logger.Level.SHOUT,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(1200));
|
||||
assertEquals(goog.debug.Logger.Level.SEVERE,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(1150));
|
||||
assertEquals(goog.debug.Logger.Level.SEVERE,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(1000));
|
||||
assertEquals(goog.debug.Logger.Level.WARNING,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(900));
|
||||
assertEquals(goog.debug.Logger.Level.INFO,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(800));
|
||||
assertEquals(goog.debug.Logger.Level.CONFIG,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(701));
|
||||
assertEquals(goog.debug.Logger.Level.CONFIG,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(700));
|
||||
assertEquals(goog.debug.Logger.Level.FINE,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(500));
|
||||
assertEquals(goog.debug.Logger.Level.FINER,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(400));
|
||||
assertEquals(goog.debug.Logger.Level.FINEST,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(300));
|
||||
assertEquals(goog.debug.Logger.Level.ALL,
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue(0));
|
||||
assertNull(goog.debug.Logger.Level.getPredefinedLevelByValue(-1));
|
||||
}
|
||||
|
||||
function TestHandler() {
|
||||
this.logRecord = null;
|
||||
}
|
||||
|
||||
TestHandler.prototype.onPublish = function(logRecord) {
|
||||
this.logRecord = logRecord;
|
||||
};
|
||||
|
||||
|
||||
TestHandler.prototype.reset = function() {
|
||||
this.logRecord = null;
|
||||
};
|
||||
|
||||
function testGetLogRecord() {
|
||||
var name = 'test.get.log.record';
|
||||
var level = 1;
|
||||
var msg = 'msg';
|
||||
|
||||
var logger = goog.debug.Logger.getLogger(name);
|
||||
var logRecord = logger.getLogRecord(level, msg);
|
||||
|
||||
assertEquals(name, logRecord.getLoggerName());
|
||||
assertEquals(level, logRecord.getLevel());
|
||||
assertEquals(msg, logRecord.getMessage());
|
||||
|
||||
assertNull(logRecord.getException());
|
||||
}
|
||||
|
||||
function testGetLogRecordWithException() {
|
||||
var name = 'test.get.log.record';
|
||||
var level = 1;
|
||||
var msg = 'msg';
|
||||
var ex = Error('Hi');
|
||||
|
||||
var logger = goog.debug.Logger.getLogger(name);
|
||||
var logRecord = logger.getLogRecord(level, msg, ex);
|
||||
|
||||
assertEquals(name, logRecord.getLoggerName());
|
||||
assertEquals(level, logRecord.getLevel());
|
||||
assertEquals(msg, logRecord.getMessage());
|
||||
assertEquals(ex, logRecord.getException());
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the LogRecord class. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.LogRecord');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* LogRecord objects are used to pass logging requests between
|
||||
* the logging framework and individual log Handlers.
|
||||
* @constructor
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {string} msg The string message.
|
||||
* @param {string} loggerName The name of the source logger.
|
||||
* @param {number=} opt_time Time this log record was created if other than now.
|
||||
* If 0, we use #goog.now.
|
||||
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
|
||||
* should only be passed in when restoring a log record from persistence.
|
||||
*/
|
||||
goog.debug.LogRecord = function(level, msg, loggerName,
|
||||
opt_time, opt_sequenceNumber) {
|
||||
this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Time the LogRecord was created.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.time_;
|
||||
|
||||
|
||||
/**
|
||||
* Level of the LogRecord
|
||||
* @type {goog.debug.Logger.Level}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.level_;
|
||||
|
||||
|
||||
/**
|
||||
* Message associated with the record
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.msg_;
|
||||
|
||||
|
||||
/**
|
||||
* Name of the logger that created the record.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.loggerName_;
|
||||
|
||||
|
||||
/**
|
||||
* Sequence number for the LogRecord. Each record has a unique sequence number
|
||||
* that is greater than all log records created before it.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Exception associated with the record
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.exception_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to enable log sequence numbers.
|
||||
*/
|
||||
goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
|
||||
|
||||
|
||||
/**
|
||||
* A sequence counter for assigning increasing sequence numbers to LogRecord
|
||||
* objects.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.nextSequenceNumber_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Sets all fields of the log record.
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {string} msg The string message.
|
||||
* @param {string} loggerName The name of the source logger.
|
||||
* @param {number=} opt_time Time this log record was created if other than now.
|
||||
* If 0, we use #goog.now.
|
||||
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
|
||||
* should only be passed in when restoring a log record from persistence.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
|
||||
opt_time, opt_sequenceNumber) {
|
||||
if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
|
||||
this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
|
||||
opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
|
||||
}
|
||||
|
||||
this.time_ = opt_time || goog.now();
|
||||
this.level_ = level;
|
||||
this.msg_ = msg;
|
||||
this.loggerName_ = loggerName;
|
||||
delete this.exception_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the source Logger's name.
|
||||
*
|
||||
* @return {string} source logger name (may be null).
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getLoggerName = function() {
|
||||
return this.loggerName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the exception that is part of the log record.
|
||||
*
|
||||
* @return {Object} the exception.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getException = function() {
|
||||
return this.exception_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the exception that is part of the log record.
|
||||
*
|
||||
* @param {Object} exception the exception.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setException = function(exception) {
|
||||
this.exception_ = exception;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the source Logger's name.
|
||||
*
|
||||
* @param {string} loggerName source logger name (may be null).
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
|
||||
this.loggerName_ = loggerName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the logging message level, for example Level.SEVERE.
|
||||
* @return {goog.debug.Logger.Level} the logging message level.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getLevel = function() {
|
||||
return this.level_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the logging message level, for example Level.SEVERE.
|
||||
* @param {goog.debug.Logger.Level} level the logging message level.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setLevel = function(level) {
|
||||
this.level_ = level;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the "raw" log message, before localization or formatting.
|
||||
*
|
||||
* @return {string} the raw message string.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getMessage = function() {
|
||||
return this.msg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the "raw" log message, before localization or formatting.
|
||||
*
|
||||
* @param {string} msg the raw message string.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setMessage = function(msg) {
|
||||
this.msg_ = msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get event time in milliseconds since 1970.
|
||||
*
|
||||
* @return {number} event time in millis since 1970.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getMillis = function() {
|
||||
return this.time_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set event time in milliseconds since 1970.
|
||||
*
|
||||
* @param {number} time event time in millis since 1970.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setMillis = function(time) {
|
||||
this.time_ = time;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the sequence number.
|
||||
* <p>
|
||||
* Sequence numbers are normally assigned in the LogRecord
|
||||
* constructor, which assigns unique sequence numbers to
|
||||
* each new LogRecord in increasing order.
|
||||
* @return {number} the sequence number.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getSequenceNumber = function() {
|
||||
return this.sequenceNumber_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2011 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Static methods for serializing and deserializing log
|
||||
* messages. These methods are deliberately kept separate from logrecord.js
|
||||
* and logger.js because they add dependencies on goog.json and goog.object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.logRecordSerializer');
|
||||
|
||||
goog.require('goog.debug.LogRecord');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of object keys used when serializing a log message.
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.Param_ = {
|
||||
TIME: 't',
|
||||
LEVEL_NAME: 'ln',
|
||||
LEVEL_VALUE: 'lv',
|
||||
MSG: 'm',
|
||||
LOGGER_NAME: 'n',
|
||||
SEQUENCE_NUMBER: 's',
|
||||
EXCEPTION: 'e'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a LogRecord to a JSON string. Note that any associated
|
||||
* exception is likely to be lost.
|
||||
* @param {goog.debug.LogRecord} record The record to serialize.
|
||||
* @return {string} Serialized JSON string of the log message.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.serialize = function(record) {
|
||||
var param = goog.debug.logRecordSerializer.Param_;
|
||||
return goog.json.serialize(goog.object.create(
|
||||
param.TIME, record.getMillis(),
|
||||
param.LEVEL_NAME, record.getLevel().name,
|
||||
param.LEVEL_VALUE, record.getLevel().value,
|
||||
param.MSG, record.getMessage(),
|
||||
param.LOGGER_NAME, record.getLoggerName(),
|
||||
param.SEQUENCE_NUMBER, record.getSequenceNumber(),
|
||||
param.EXCEPTION, record.getException() && record.getException().message));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes a JSON-serialized LogRecord.
|
||||
* @param {string} s The JSON serialized record.
|
||||
* @return {!goog.debug.LogRecord} The deserialized record.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.parse = function(s) {
|
||||
return goog.debug.logRecordSerializer.reconstitute_(goog.json.parse(s));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes a JSON-serialized LogRecord. Use this only if you're
|
||||
* naive enough to blindly trust any JSON formatted input that comes
|
||||
* your way.
|
||||
* @param {string} s The JSON serialized record.
|
||||
* @return {!goog.debug.LogRecord} The deserialized record.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.unsafeParse = function(s) {
|
||||
return goog.debug.logRecordSerializer.reconstitute_(goog.json.unsafeParse(s));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Common reconsitution method for for parse and unsafeParse.
|
||||
* @param {Object} o The JSON object.
|
||||
* @return {!goog.debug.LogRecord} The reconstituted record.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.reconstitute_ = function(o) {
|
||||
var param = goog.debug.logRecordSerializer.Param_;
|
||||
var level = goog.debug.logRecordSerializer.getLevel_(
|
||||
o[param.LEVEL_NAME], o[param.LEVEL_VALUE]);
|
||||
|
||||
var ret = new goog.debug.LogRecord(level, o[param.MSG],
|
||||
o[param.LOGGER_NAME], o[param.TIME], o[param.SEQUENCE_NUMBER]);
|
||||
var exceptionMessage = o[param.EXCEPTION];
|
||||
if (goog.isDefAndNotNull(exceptionMessage)) {
|
||||
ret.setException(new Error(exceptionMessage));
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the log level to return.
|
||||
* @param {number} value The numeric value of the log level to return.
|
||||
* @return {!goog.debug.Logger.Level} Returns a goog.debug.Logger.Level with
|
||||
* the specified name and value. If the name and value match a predefined
|
||||
* log level, that instance will be returned, otherwise a new one will be
|
||||
* created.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.getLevel_ = function(name, value) {
|
||||
var level = goog.debug.Logger.Level.getPredefinedLevel(name);
|
||||
return level && level.value == value ?
|
||||
level : new goog.debug.Logger.Level(name, value);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.debug.Logger
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.logRecordSerializerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2011 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.logRecordSerializerTest');
|
||||
goog.setTestOnly('goog.debug.logRecordSerializerTest');
|
||||
|
||||
goog.require('goog.debug.LogRecord');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.logRecordSerializer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var NOW = 1311484654000;
|
||||
var SEQ = 1231;
|
||||
|
||||
function testBasic() {
|
||||
var rec = new goog.debug.LogRecord(goog.debug.Logger.Level.FINE,
|
||||
'An awesome message', 'logger.name', NOW, SEQ);
|
||||
var thawed = goog.debug.logRecordSerializer.parse(
|
||||
goog.debug.logRecordSerializer.serialize(rec));
|
||||
|
||||
assertEquals(goog.debug.Logger.Level.FINE, thawed.getLevel());
|
||||
assertEquals('An awesome message', thawed.getMessage());
|
||||
assertEquals('logger.name', thawed.getLoggerName());
|
||||
assertEquals(NOW, thawed.getMillis());
|
||||
assertEquals(SEQ, thawed.getSequenceNumber());
|
||||
assertNull(thawed.getException());
|
||||
}
|
||||
|
||||
function testUnsafeParse() {
|
||||
var rec = new goog.debug.LogRecord(goog.debug.Logger.Level.FINE,
|
||||
'An awesome message', 'logger.name', NOW, SEQ);
|
||||
var thawed = goog.debug.logRecordSerializer.parse(
|
||||
goog.debug.logRecordSerializer.serialize(rec));
|
||||
|
||||
assertEquals(goog.debug.Logger.Level.FINE, thawed.getLevel());
|
||||
assertEquals('An awesome message', thawed.getMessage());
|
||||
assertEquals('logger.name', thawed.getLoggerName());
|
||||
assertEquals(NOW, thawed.getMillis());
|
||||
assertEquals(SEQ, thawed.getSequenceNumber());
|
||||
assertNull(thawed.getException());
|
||||
}
|
||||
|
||||
function testWithException() {
|
||||
var err = new Error('it broke!');
|
||||
var rec = new goog.debug.LogRecord(goog.debug.Logger.Level.FINE,
|
||||
'An awesome message', 'logger.name', NOW, SEQ);
|
||||
rec.setException(err);
|
||||
var thawed = goog.debug.logRecordSerializer.unsafeParse(
|
||||
goog.debug.logRecordSerializer.serialize(rec));
|
||||
assertEquals(err.message, thawed.getException().message);
|
||||
}
|
||||
|
||||
function testCustomLogLevel() {
|
||||
var rec = new goog.debug.LogRecord(
|
||||
new goog.debug.Logger.Level('CUSTOM', -1),
|
||||
'An awesome message', 'logger.name', NOW, SEQ);
|
||||
var thawed = goog.debug.logRecordSerializer.parse(
|
||||
goog.debug.logRecordSerializer.serialize(rec));
|
||||
|
||||
assertEquals('CUSTOM', thawed.getLevel().name);
|
||||
assertEquals(-1, thawed.getLevel().value);
|
||||
}
|
||||
|
||||
function testWeirdLogLevel() {
|
||||
var rec = new goog.debug.LogRecord(
|
||||
new goog.debug.Logger.Level('FINE', -1),
|
||||
'An awesome message', 'logger.name', NOW, SEQ);
|
||||
var thawed = goog.debug.logRecordSerializer.parse(
|
||||
goog.debug.logRecordSerializer.serialize(rec));
|
||||
|
||||
assertEquals('FINE', thawed.getLevel().name);
|
||||
// Makes sure that the log leve is still -1 even though the name
|
||||
// FINE is predefind.
|
||||
assertEquals(-1, thawed.getLevel().value);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2007 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition the goog.debug.RelativeTimeProvider class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.RelativeTimeProvider');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple object to keep track of a timestamp considered the start of
|
||||
* something. The main use is for the logger system to maintain a start time
|
||||
* that is occasionally reset. For example, in Gmail, we reset this relative
|
||||
* time at the start of a user action so that timings are offset from the
|
||||
* beginning of the action. This class also provides a singleton as the default
|
||||
* behavior for most use cases is to share the same start time.
|
||||
*
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider = function() {
|
||||
/**
|
||||
* The start time.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.relativeTimeStart_ = goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default instance.
|
||||
* @type {goog.debug.RelativeTimeProvider}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.defaultInstance_ =
|
||||
new goog.debug.RelativeTimeProvider();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the start time to the specified time.
|
||||
* @param {number} timeStamp The start time.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
|
||||
this.relativeTimeStart_ = timeStamp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the start time to now.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.reset = function() {
|
||||
this.set(goog.now());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The start time.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.get = function() {
|
||||
return this.relativeTimeStart_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.debug.RelativeTimeProvider} The default instance.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
|
||||
return goog.debug.RelativeTimeProvider.defaultInstance_;
|
||||
};
|
||||
@@ -0,0 +1,725 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Tracer class and associated classes.
|
||||
*
|
||||
* @see ../demos/tracer.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Trace');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.structs.SimplePool');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class used for singleton goog.debug.Trace. Used for timing slow points in
|
||||
* the code. Based on the java Tracer class but optimized for javascript.
|
||||
* See com.google.common.tracing.Tracer.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_ = function() {
|
||||
|
||||
/**
|
||||
* Events in order.
|
||||
* @type {Array<goog.debug.Trace_.Event_>}
|
||||
* @private
|
||||
*/
|
||||
this.events_ = [];
|
||||
|
||||
/**
|
||||
* Outstanding events that have started but haven't yet ended. The keys are
|
||||
* numeric ids and the values are goog.debug.Trace_.Event_ objects.
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.outstandingEvents_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* Start time of the event trace
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.startTime_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to startTracer
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadStart_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to endTracer
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadEnd_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to addComment
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadComment_ = 0;
|
||||
|
||||
/**
|
||||
* Keeps stats on different types of tracers. The keys are strings and the
|
||||
* values are goog.debug.Stat
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.stats_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* Total number of traces created in the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerCount_ = 0;
|
||||
|
||||
/**
|
||||
* Total number of comments created in the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.commentCount_ = 0;
|
||||
|
||||
/**
|
||||
* Next id to use for the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.nextId_ = 1;
|
||||
|
||||
/**
|
||||
* A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
|
||||
* garbage collecting these (which is very expensive in IE6).
|
||||
* @type {goog.structs.SimplePool}
|
||||
* @private
|
||||
*/
|
||||
this.eventPool_ = new goog.structs.SimplePool(0, 4000);
|
||||
this.eventPool_.createObject = function() {
|
||||
return new goog.debug.Trace_.Event_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
|
||||
* garbage collecting these (which is very expensive in IE6).
|
||||
* @type {goog.structs.SimplePool}
|
||||
* @private
|
||||
*/
|
||||
this.statPool_ = new goog.structs.SimplePool(0, 50);
|
||||
this.statPool_.createObject = function() {
|
||||
return new goog.debug.Trace_.Stat_();
|
||||
};
|
||||
|
||||
var that = this;
|
||||
this.idPool_ = new goog.structs.SimplePool(0, 2000);
|
||||
|
||||
// TODO(nicksantos): SimplePool is supposed to only return objects.
|
||||
// Reconcile this so that we don't have to cast to number below.
|
||||
this.idPool_.createObject = function() {
|
||||
return String(that.nextId_++);
|
||||
};
|
||||
this.idPool_.disposeObject = function(obj) {};
|
||||
|
||||
/**
|
||||
* Default threshold below which a tracer shouldn't be reported
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.defaultThreshold_ = 3;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logger for the tracer
|
||||
* @type {goog.log.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.prototype.logger_ =
|
||||
goog.log.getLogger('goog.debug.Trace');
|
||||
|
||||
|
||||
/**
|
||||
* Maximum size of the trace before we discard events
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Event type supported by tracer
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.debug.Trace_.EventType = {
|
||||
/**
|
||||
* Start event type
|
||||
*/
|
||||
START: 0,
|
||||
|
||||
/**
|
||||
* Stop event type
|
||||
*/
|
||||
STOP: 1,
|
||||
|
||||
/**
|
||||
* Comment event type
|
||||
*/
|
||||
COMMENT: 2
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class to keep track of a stat of a single tracer type. Stores the count
|
||||
* and cumulative time.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.Stat_ = function() {
|
||||
/**
|
||||
* Number of tracers
|
||||
* @type {number}
|
||||
*/
|
||||
this.count = 0;
|
||||
|
||||
/**
|
||||
* Cumulative time of traces
|
||||
* @type {number}
|
||||
*/
|
||||
this.time = 0;
|
||||
|
||||
/**
|
||||
* Total number of allocations for this tracer type
|
||||
* @type {number}
|
||||
*/
|
||||
this.varAlloc = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string|null|undefined}
|
||||
*/
|
||||
goog.debug.Trace_.Stat_.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} A string describing the tracer stat.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.Stat_.prototype.toString = function() {
|
||||
var sb = [];
|
||||
sb.push(this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
|
||||
' ms)');
|
||||
if (this.varAlloc) {
|
||||
sb.push(' [VarAlloc = ', this.varAlloc, ']');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Private class used to encapsulate a single event, either the start or stop
|
||||
* of a tracer.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.Event_ = function() {
|
||||
// the fields are different for different events - see usage in code
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string|null|undefined}
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string for the event.
|
||||
* @param {number} startTime The start time of the trace to generate relative
|
||||
* times.
|
||||
* @param {number} prevTime The completion time of the previous event or -1.
|
||||
* @param {string} indent Extra indent for the message
|
||||
* if there was no previous event.
|
||||
* @return {string} The formatted tracer string.
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.toTraceString = function(startTime, prevTime,
|
||||
indent) {
|
||||
var sb = [];
|
||||
|
||||
if (prevTime == -1) {
|
||||
sb.push(' ');
|
||||
} else {
|
||||
sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
|
||||
}
|
||||
|
||||
sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
|
||||
if (this.eventType == goog.debug.Trace_.EventType.START) {
|
||||
sb.push(' Start ');
|
||||
} else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
|
||||
sb.push(' Done ');
|
||||
var delta = this.stopTime - this.startTime;
|
||||
sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
|
||||
} else {
|
||||
sb.push(' Comment ');
|
||||
}
|
||||
|
||||
sb.push(indent, this);
|
||||
if (this.totalVarAlloc > 0) {
|
||||
sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} A string describing the tracer event.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.toString = function() {
|
||||
if (this.type == null) {
|
||||
return this.comment;
|
||||
} else {
|
||||
return '[' + this.type + '] ' + this.comment;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add the ability to explicitly set the start time. This is useful for example
|
||||
* for measuring initial load time where you can set a variable as soon as the
|
||||
* main page of the app is loaded and then later call this function when the
|
||||
* Tracer code has been loaded.
|
||||
* @param {number} startTime The start time to set.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.setStartTime = function(startTime) {
|
||||
this.startTime_ = startTime;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes and resets the current trace
|
||||
* @param {number} defaultThreshold The default threshold below which the
|
||||
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
|
||||
this.reset(defaultThreshold);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the current trace
|
||||
*/
|
||||
goog.debug.Trace_.prototype.clearCurrentTrace = function() {
|
||||
this.reset(0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the trace.
|
||||
* @param {number} defaultThreshold The default threshold below which the
|
||||
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
|
||||
this.defaultThreshold_ = defaultThreshold;
|
||||
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var id = /** @type {Object} */ (this.eventPool_).id;
|
||||
if (id) {
|
||||
this.idPool_.releaseObject(id);
|
||||
}
|
||||
this.eventPool_.releaseObject(this.events_[i]);
|
||||
}
|
||||
|
||||
this.events_.length = 0;
|
||||
this.outstandingEvents_.clear();
|
||||
this.startTime_ = goog.debug.Trace_.now();
|
||||
this.tracerOverheadStart_ = 0;
|
||||
this.tracerOverheadEnd_ = 0;
|
||||
this.tracerOverheadComment_ = 0;
|
||||
this.tracerCount_ = 0;
|
||||
this.commentCount_ = 0;
|
||||
|
||||
var keys = this.stats_.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var stat = this.stats_.get(key);
|
||||
stat.count = 0;
|
||||
stat.time = 0;
|
||||
stat.varAlloc = 0;
|
||||
this.statPool_.releaseObject(/** @type {Object} */ (stat));
|
||||
}
|
||||
this.stats_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts a tracer
|
||||
* @param {string} comment A comment used to identify the tracer. Does not
|
||||
* need to be unique.
|
||||
* @param {string=} opt_type Type used to identify the tracer. If a Trace is
|
||||
* given a type (the first argument to the constructor) and multiple Traces
|
||||
* are done on that type then a "TOTAL line will be produced showing the
|
||||
* total number of traces and the sum of the time
|
||||
* ("TOTAL Database 2 (37 ms)" in our example). These traces should be
|
||||
* mutually exclusive or else the sum won't make sense (the time will
|
||||
* be double counted if the second starts before the first ends).
|
||||
* @return {number} The identifier for the tracer that should be passed to the
|
||||
* the stopTracer method.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
|
||||
var tracerStartTime = goog.debug.Trace_.now();
|
||||
var varAlloc = this.getTotalVarAlloc();
|
||||
var outstandingEventCount = this.outstandingEvents_.getCount();
|
||||
if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
|
||||
goog.log.warning(this.logger_,
|
||||
'Giant thread trace. Clearing to avoid memory leak.');
|
||||
// This is the more likely case. This usually means that we
|
||||
// either forgot to clear the trace or else we are performing a
|
||||
// very large number of events
|
||||
if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var event = this.events_[i];
|
||||
if (event.id) {
|
||||
this.idPool_.releaseObject(event.id);
|
||||
}
|
||||
this.eventPool_.releaseObject(event);
|
||||
}
|
||||
this.events_.length = 0;
|
||||
}
|
||||
|
||||
// This is less likely and probably indicates that a lot of traces
|
||||
// aren't being closed. We want to avoid unnecessarily clearing
|
||||
// this though in case the events do eventually finish.
|
||||
if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
|
||||
this.outstandingEvents_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
goog.debug.Logger.logToProfilers('Start : ' + comment);
|
||||
|
||||
var event = /** @type {goog.debug.Trace_.Event_} */ (
|
||||
this.eventPool_.getObject());
|
||||
event.totalVarAlloc = varAlloc;
|
||||
event.eventType = goog.debug.Trace_.EventType.START;
|
||||
event.id = Number(this.idPool_.getObject());
|
||||
event.comment = comment;
|
||||
event.type = opt_type;
|
||||
this.events_.push(event);
|
||||
this.outstandingEvents_.set(String(event.id), event);
|
||||
this.tracerCount_++;
|
||||
var now = goog.debug.Trace_.now();
|
||||
event.startTime = event.eventTime = now;
|
||||
this.tracerOverheadStart_ += now - tracerStartTime;
|
||||
return event.id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops a tracer
|
||||
* @param {number|undefined|null} id The id of the tracer that is ending.
|
||||
* @param {number=} opt_silenceThreshold Threshold below which the tracer is
|
||||
* silenced.
|
||||
* @return {?number} The elapsed time for the tracer or null if the tracer
|
||||
* identitifer was not recognized.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
|
||||
// this used to call goog.isDef(opt_silenceThreshold) but that causes an
|
||||
// object allocation in IE for some reason (doh!). The following code doesn't
|
||||
// cause an allocation
|
||||
var now = goog.debug.Trace_.now();
|
||||
var silenceThreshold;
|
||||
if (opt_silenceThreshold === 0) {
|
||||
silenceThreshold = 0;
|
||||
} else if (opt_silenceThreshold) {
|
||||
silenceThreshold = opt_silenceThreshold;
|
||||
} else {
|
||||
silenceThreshold = this.defaultThreshold_;
|
||||
}
|
||||
|
||||
var startEvent = this.outstandingEvents_.get(String(id));
|
||||
if (startEvent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.outstandingEvents_.remove(String(id));
|
||||
|
||||
var stopEvent;
|
||||
var elapsed = now - startEvent.startTime;
|
||||
if (elapsed < silenceThreshold) {
|
||||
var count = this.events_.length;
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
var nextEvent = this.events_[i];
|
||||
if (nextEvent == startEvent) {
|
||||
this.events_.splice(i, 1);
|
||||
this.idPool_.releaseObject(startEvent.id);
|
||||
this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
stopEvent = /** @type {goog.debug.Trace_.Event_} */ (
|
||||
this.eventPool_.getObject());
|
||||
stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
|
||||
stopEvent.startTime = startEvent.startTime;
|
||||
stopEvent.comment = startEvent.comment;
|
||||
stopEvent.type = startEvent.type;
|
||||
stopEvent.stopTime = stopEvent.eventTime = now;
|
||||
|
||||
this.events_.push(stopEvent);
|
||||
}
|
||||
|
||||
var type = startEvent.type;
|
||||
var stat = null;
|
||||
if (type) {
|
||||
stat = this.getStat_(type);
|
||||
stat.count++;
|
||||
stat.time += elapsed;
|
||||
}
|
||||
if (stopEvent) {
|
||||
goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);
|
||||
|
||||
stopEvent.totalVarAlloc = this.getTotalVarAlloc();
|
||||
|
||||
if (stat) {
|
||||
stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
|
||||
}
|
||||
}
|
||||
var tracerFinishTime = goog.debug.Trace_.now();
|
||||
this.tracerOverheadEnd_ += tracerFinishTime - now;
|
||||
return elapsed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the ActiveX object that can be used to get GC tracing in IE6.
|
||||
* @param {Object} gcTracer GCTracer ActiveX object.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
|
||||
this.gcTracer_ = gcTracer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of allocations since the GC stats were reset. Only
|
||||
* works in IE.
|
||||
* @return {number} The number of allocaitons or -1 if not supported.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
|
||||
var gcTracer = this.gcTracer_;
|
||||
// isTracing is defined on the ActiveX object.
|
||||
if (gcTracer && gcTracer['isTracing']()) {
|
||||
return gcTracer['totalVarAlloc'];
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a comment to the trace. Makes it possible to see when a specific event
|
||||
* happened in relation to the traces.
|
||||
* @param {string} comment A comment that is inserted into the trace.
|
||||
* @param {?string=} opt_type Type used to identify the tracer. If a comment is
|
||||
* given a type and multiple comments are done on that type then a "TOTAL
|
||||
* line will be produced showing the total number of comments of that type.
|
||||
* @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
|
||||
* specified, the current time wil be used.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.addComment = function(comment, opt_type,
|
||||
opt_timeStamp) {
|
||||
var now = goog.debug.Trace_.now();
|
||||
var timeStamp = opt_timeStamp ? opt_timeStamp : now;
|
||||
|
||||
var eventComment = /** @type {goog.debug.Trace_.Event_} */ (
|
||||
this.eventPool_.getObject());
|
||||
eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
|
||||
eventComment.eventTime = timeStamp;
|
||||
eventComment.type = opt_type;
|
||||
eventComment.comment = comment;
|
||||
eventComment.totalVarAlloc = this.getTotalVarAlloc();
|
||||
this.commentCount_++;
|
||||
|
||||
if (opt_timeStamp) {
|
||||
var numEvents = this.events_.length;
|
||||
for (var i = 0; i < numEvents; i++) {
|
||||
var event = this.events_[i];
|
||||
var eventTime = event.eventTime;
|
||||
|
||||
if (eventTime > timeStamp) {
|
||||
goog.array.insertAt(this.events_, eventComment, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == numEvents) {
|
||||
this.events_.push(eventComment);
|
||||
}
|
||||
} else {
|
||||
this.events_.push(eventComment);
|
||||
}
|
||||
|
||||
var type = eventComment.type;
|
||||
if (type) {
|
||||
var stat = this.getStat_(type);
|
||||
stat.count++;
|
||||
}
|
||||
|
||||
this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a stat object for a particular type. The stat object is created if it
|
||||
* hasn't yet been.
|
||||
* @param {string} type The type of stat.
|
||||
* @return {goog.debug.Trace_.Stat_} The stat object.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getStat_ = function(type) {
|
||||
var stat = this.stats_.get(type);
|
||||
if (!stat) {
|
||||
stat = /** @type {goog.debug.Trace_.Event_} */ (
|
||||
this.statPool_.getObject());
|
||||
stat.type = type;
|
||||
this.stats_.set(type, stat);
|
||||
}
|
||||
return /** @type {goog.debug.Trace_.Stat_} */(stat);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string for the current trace
|
||||
* @return {string} A formatted string that shows the timings of the current
|
||||
* trace.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getFormattedTrace = function() {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string that describes the thread trace.
|
||||
* @return {string} A formatted string.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.prototype.toString = function() {
|
||||
var sb = [];
|
||||
var etime = -1;
|
||||
var indent = [];
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var e = this.events_[i];
|
||||
if (e.eventType == goog.debug.Trace_.EventType.STOP) {
|
||||
indent.pop();
|
||||
}
|
||||
sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
|
||||
etime = e.eventTime;
|
||||
sb.push('\n');
|
||||
if (e.eventType == goog.debug.Trace_.EventType.START) {
|
||||
indent.push('| ');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.outstandingEvents_.getCount() != 0) {
|
||||
var now = goog.debug.Trace_.now();
|
||||
|
||||
sb.push(' Unstopped timers:\n');
|
||||
goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
|
||||
sb.push(' ', startEvent, ' (', now - startEvent.startTime,
|
||||
' ms, started at ',
|
||||
goog.debug.Trace_.formatTime_(startEvent.startTime),
|
||||
')\n');
|
||||
});
|
||||
}
|
||||
|
||||
var statKeys = this.stats_.getKeys();
|
||||
for (var i = 0; i < statKeys.length; i++) {
|
||||
var stat = this.stats_.get(statKeys[i]);
|
||||
if (stat.count > 1) {
|
||||
sb.push(' TOTAL ', stat, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
sb.push('Total tracers created ', this.tracerCount_, '\n',
|
||||
'Total comments created ', this.commentCount_, '\n',
|
||||
'Overhead start: ', this.tracerOverheadStart_, ' ms\n',
|
||||
'Overhead end: ', this.tracerOverheadEnd_, ' ms\n',
|
||||
'Overhead comment: ', this.tracerOverheadComment_, ' ms\n');
|
||||
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts 'v' to a string and pads it with up to 3 spaces for
|
||||
* improved alignment. TODO there must be a better way
|
||||
* @param {number} v A number.
|
||||
* @return {string} A padded string.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.longToPaddedString_ = function(v) {
|
||||
v = Math.round(v);
|
||||
// todo (pupius) - there should be a generic string in goog.string for this
|
||||
var space = '';
|
||||
if (v < 1000) space = ' ';
|
||||
if (v < 100) space = ' ';
|
||||
if (v < 10) space = ' ';
|
||||
return space + v;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the sec.ms part of time (if time = "20:06:11.566", "11.566
|
||||
* @param {number} time The time in MS.
|
||||
* @return {string} A formatted string as sec.ms'.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.formatTime_ = function(time) {
|
||||
time = Math.round(time);
|
||||
var sec = (time / 1000) % 60;
|
||||
var ms = time % 1000;
|
||||
|
||||
// TODO their must be a nicer way to get zero padded integers
|
||||
return String(100 + sec).substring(1, 3) + '.' +
|
||||
String(1000 + ms).substring(1, 4);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current time. Done through a wrapper function so it can be
|
||||
* overridden by application code. Gmail has an ActiveX extension that provides
|
||||
* higher precision timing info.
|
||||
* @return {number} The current time in milliseconds.
|
||||
*/
|
||||
goog.debug.Trace_.now = function() {
|
||||
return goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton trace object
|
||||
* @type {goog.debug.Trace_}
|
||||
*/
|
||||
goog.debug.Trace = new goog.debug.Trace_();
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
Closure Unit Tests - goog.util.Tracer
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.debug.TraceTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2006 The Closure Library 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.
|
||||
|
||||
goog.provide('goog.debug.TraceTest');
|
||||
goog.setTestOnly('goog.debug.TraceTest');
|
||||
|
||||
goog.require('goog.debug.Trace');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
function testTracer() {
|
||||
goog.debug.Trace.initCurrentTrace();
|
||||
var t = goog.debug.Trace.startTracer('foo');
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
sum += i;
|
||||
}
|
||||
goog.debug.Trace.stopTracer(t);
|
||||
var trace = goog.debug.Trace.getFormattedTrace();
|
||||
var lines = trace.split('\n');
|
||||
assertEquals(8, lines.length);
|
||||
assertNotNull(lines[0].match(/^\s*\d+\.\d+\s+Start\s+foo$/));
|
||||
assertNotNull(lines[1].match(/^\s*\d+\s+\d+\.\d+\s+Done\s+\d+ ms\s+foo$/));
|
||||
}
|
||||
|
||||
function testPerf() {
|
||||
goog.debug.Trace.initCurrentTrace();
|
||||
var count = 1000;
|
||||
var start = goog.now();
|
||||
for (var i = 0; i < count; i++) {
|
||||
var t = goog.debug.Trace.startTracer('foo');
|
||||
var t2 = goog.debug.Trace.startTracer('foo.bar');
|
||||
var t3 = goog.debug.Trace.startTracer('foo.bar.baz');
|
||||
goog.debug.Trace.stopTracer(t3);
|
||||
var t4 = goog.debug.Trace.startTracer('foo.bar.bim');
|
||||
goog.debug.Trace.stopTracer(t4);
|
||||
goog.debug.Trace.stopTracer(t2);
|
||||
goog.debug.Trace.stopTracer(t);
|
||||
}
|
||||
count *= 4;
|
||||
var end = goog.now();
|
||||
}
|
||||
Reference in New Issue
Block a user