Adding mapbox-gl branch

This commit is contained in:
Andreas Hocevar
2015-03-16 18:50:27 +01:00
parent 7985f030fa
commit 57ee7f52fd
3109 changed files with 943365 additions and 0 deletions
@@ -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&quot;<br>' +
'Url: <a href="view-source:http://fileName%22" ' +
'target="_new">http://fileName&quot;</a><br>' +
'Line: lineNumber&quot;<br><br>' +
'Browser stack:<br>' +
'stack&quot;-&gt; [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 \\[.*?\\] \\[ &#160;.*?s\\] \\[loggerName\\] ' +
'<span class="dbg-f">mess<br> &#160;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();
}