initial commit

This commit is contained in:
2026-06-25 21:30:32 +00:00
commit 328faf6251
220 changed files with 162103 additions and 0 deletions
+747
View File
@@ -0,0 +1,747 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Logging and debugging utilities.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug');
goog.require('goog.array');
goog.require('goog.debug.errorcontext');
/** @define {boolean} Whether logging should be enabled. */
goog.debug.LOGGING_ENABLED =
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
/** @define {boolean} Whether to force "sloppy" stack building. */
goog.debug.FORCE_SLOPPY_STACKS =
goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);
/**
* @define {boolean} TODO(user): Remove this hack once bug is resolved.
*/
goog.debug.CHECK_FOR_THROWN_EVENT =
goog.define('goog.debug.CHECK_FOR_THROWN_EVENT', false);
/**
* 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.
* @suppress {strictMissingProperties} onerror is not defined as a property
* on Object.
*/
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
'use strict';
var target = opt_target || goog.global;
var oldErrorHandler = target.onerror;
var retVal = !!opt_cancel;
/**
* 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) {
'use strict';
if (oldErrorHandler) {
oldErrorHandler(message, url, line, opt_col, opt_error);
}
logFunc({
message: message,
fileName: url,
line: line,
lineNumber: 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 `obj`.
*/
goog.debug.expose = function(obj, opt_showFn) {
'use strict';
if (typeof obj == 'undefined') {
return 'undefined';
}
if (obj == null) {
return 'NULL';
}
var str = [];
for (var x in obj) {
if (!opt_showFn && typeof obj[x] === 'function') {
continue;
}
var s = x + ' = ';
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. NOTE: The output will include
* Uids on all objects that were exposed. Any added Uids will be removed before
* returning.
* @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 `obj`.
*/
goog.debug.deepExpose = function(obj, opt_showFn) {
'use strict';
var str = [];
// Track any objects where deepExpose added a Uid, so they can be cleaned up
// before return. We do this globally, rather than only on ancestors so that
// if the same object appears in the output, you can see it.
var uidsToCleanup = [];
var ancestorUids = {};
var helper = function(obj, space) {
'use strict';
var nestspace = space + ' ';
var indentMultiline = function(str) {
'use strict';
return str.replace(/\n/g, '\n' + space);
};
try {
if (obj === undefined) {
str.push('undefined');
} else if (obj === null) {
str.push('NULL');
} else if (typeof obj === 'string') {
str.push('"' + indentMultiline(obj) + '"');
} else if (typeof obj === 'function') {
str.push(indentMultiline(String(obj)));
} else if (goog.isObject(obj)) {
// Add a Uid if needed. The struct calls implicitly adds them.
if (!goog.hasUid(obj)) {
uidsToCleanup.push(obj);
}
var uid = goog.getUid(obj);
if (ancestorUids[uid]) {
str.push('*** reference loop detected (id=' + uid + ') ***');
} else {
ancestorUids[uid] = true;
str.push('{');
for (var x in obj) {
if (!opt_showFn && typeof obj[x] === 'function') {
continue;
}
str.push('\n');
str.push(nestspace);
str.push(x + ' = ');
helper(obj[x], nestspace);
}
str.push('\n' + space + '}');
delete ancestorUids[uid];
}
} else {
str.push(obj);
}
} catch (e) {
str.push('*** ' + e + ' ***');
}
};
helper(obj, '');
// Cleanup any Uids that were added by the deepExpose.
for (var i = 0; i < uidsToCleanup.length; i++) {
goog.removeUid(uidsToCleanup[i]);
}
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) {
'use strict';
var str = [];
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
str.push(goog.debug.exposeArray(arr[i]));
} else {
str.push(arr[i]);
}
}
return '[ ' + str.join(', ') + ' ]';
};
/**
* Normalizes the error/exception object between browsers.
* @param {*} err Raw error object.
* @return {{
* message: (?|undefined),
* name: (?|undefined),
* lineNumber: (?|undefined),
* fileName: (?|undefined),
* stack: (?|undefined)
* }} Representation of err as an Object. It will never return err.
* @suppress {strictMissingProperties} properties not defined on err
*/
goog.debug.normalizeErrorObject = function(err) {
'use strict';
var href = goog.getObjectByName('window.location.href');
if (err == null) {
err = 'Unknown Error of type "null/undefined"';
}
if (typeof err === 'string') {
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;
}
var stack = goog.debug.serializeErrorStack_(err);
// 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) {
var message = err.message;
if (message == null) {
if (err.constructor && err.constructor instanceof Function) {
var ctorName = err.constructor.name ?
err.constructor.name :
goog.debug.getFunctionName(err.constructor);
message = 'Unknown Error of type "' + ctorName + '"';
// TODO(user): Remove this hack once bug is resolved.
if (goog.debug.CHECK_FOR_THROWN_EVENT && ctorName == 'Event') {
try {
message = message + ' with Event.type "' + (err.type || '') + '"';
} catch (e) {
// Just give up on getting more information out of the error object.
}
}
} else {
message = 'Unknown Error of unknown type';
}
// Avoid TypeError since toString could be missing from the instance
// (e.g. if created Object.create(null)).
if (typeof err.toString === 'function' &&
Object.prototype.toString !== err.toString) {
message += ': ' + err.toString();
}
}
return {
'message': message,
'name': err.name || 'UnknownError',
'lineNumber': lineNumber,
'fileName': fileName,
'stack': stack || 'Not available'
};
}
// Standards error object
// Typed !Object. Should be a subtype of the return type, but it's not.
err.stack = stack;
// Return non-standard error to allow for consistent result (eg. enumerable).
return {
'message': err.message,
'name': err.name,
'lineNumber': err.lineNumber,
'fileName': err.fileName,
'stack': err.stack
};
};
/**
* Serialize stack by including the cause chain of the exception if it exists.
*
*
* @param {*} e an exception that may have a cause
* @param {!Object=} seen set of cause that have already been serialized
* @return {string}
* @private
* @suppress {missingProperties} properties not defined on cause and e
*/
goog.debug.serializeErrorStack_ = function(e, seen) {
'use strict';
if (!seen) {
seen = {};
}
seen[goog.debug.serializeErrorAsKey_(e)] = true;
var stack = e['stack'] || '';
// Add cause if exists.
var cause = e.cause;
if (cause && !seen[goog.debug.serializeErrorAsKey_(cause)]) {
stack += '\nCaused by: ';
// Some browsers like Chrome add the error message as the first frame of the
// stack, In this case we don't need to add it. Note: we don't use
// String.startsWith method because it might have to be polyfilled.
if (!cause.stack || cause.stack.indexOf(cause.toString()) != 0) {
stack += (typeof cause === 'string') ? cause : cause.message + '\n';
}
stack += goog.debug.serializeErrorStack_(cause, seen);
}
return stack;
};
/**
* Serialize an error to a string key.
* @param {*} e an exception
* @return {string}
* @private
*/
goog.debug.serializeErrorAsKey_ = function(e) {
'use strict';
var keyPrefix = '';
if (typeof e.toString === 'function') {
keyPrefix = '' + e;
}
return keyPrefix + e['stack'];
};
/**
* Converts an object to an Error using the object's toString if it's not
* already an Error, adds a stacktrace if there isn't one, and optionally adds
* an extra message.
* @param {*} err The original thrown error, object, or string.
* @param {string=} opt_message optional additional message to add to the
* error.
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
* it is converted to an Error which is enhanced and returned.
*/
goog.debug.enhanceError = function(err, opt_message) {
'use strict';
var error;
if (!(err instanceof Error)) {
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;
};
/**
* Converts an object to an Error using the object's toString if it's not
* already an Error, adds a stacktrace if there isn't one, and optionally adds
* context to the Error, which is reported by the closure error reporter.
* @param {*} err The original thrown error, object, or string.
* @param {!Object<string, string>=} opt_context Key-value context to add to the
* Error.
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
* it is converted to an Error which is enhanced and returned.
*/
goog.debug.enhanceErrorWithContext = function(err, opt_context) {
'use strict';
var error = goog.debug.enhanceError(err);
if (opt_context) {
for (var key in opt_context) {
goog.debug.errorcontext.addErrorContext(error, key, opt_context[key]);
}
}
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) {
'use strict';
if (!goog.debug.FORCE_SLOPPY_STACKS) {
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');
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) {
'use strict';
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=} fn If provided, when collecting the stack trace all
* frames above the topmost call to this function, including that call,
* will be left out of the stack trace.
* @return {string} Stack trace.
* @suppress {es5Strict}
*/
goog.debug.getStacktrace = function(fn) {
'use strict';
var stack;
if (!goog.debug.FORCE_SLOPPY_STACKS) {
// Try to get the stack trace from the environment if it is available.
var contextFn = 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_(fn || arguments.callee.caller, []);
}
return stack;
};
/**
* Private helper for getStacktrace().
* @param {?Function} fn If provided, when collecting the stack trace all
* frames above the topmost call to this function, including that call,
* will be left out of the stack trace.
* @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) {
'use strict';
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');
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('');
};
/**
* Gets a function name
* @param {Function} fn Function to get name of.
* @return {string} Function's name.
*/
goog.debug.getFunctionName = function(fn) {
'use strict';
if (goog.debug.fnNameCache_[fn]) {
return goog.debug.fnNameCache_[fn];
}
// Heuristically determine function name based on code.
var functionSource = String(fn);
if (!goog.debug.fnNameCache_[functionSource]) {
var matches = /function\s+([^\(]+)/m.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) {
'use strict';
return string.replace(/ /g, '[_]')
.replace(/\f/g, '[f]')
.replace(/\n/g, '[n]\n')
.replace(/\r/g, '[r]')
.replace(/\t/g, '[t]');
};
/**
* Returns the type of a value. If a constructor is passed, and a suitable
* string cannot be found, 'unknown type name' will be returned.
*
* <p>Forked rather than moved from {@link goog.asserts.getType_}
* to avoid adding a dependency to goog.asserts.
* @param {*} value A constructor, object, or primitive.
* @return {string} The best display name for the value, or 'unknown type name'.
*/
goog.debug.runtimeType = function(value) {
'use strict';
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return /** @type {string} */ (value.constructor.displayName) ||
value.constructor.name || Object.prototype.toString.call(value);
} else {
return value === null ? 'null' : typeof value;
}
};
/**
* Hash map for storing function names that have already been looked up.
* @type {Object}
* @private
*/
goog.debug.fnNameCache_ = {};
/**
* Private internal function to support goog.debug.freeze.
* @param {T} arg
* @return {T}
* @template T
* @private
*/
goog.debug.freezeInternal_ = goog.DEBUG && Object.freeze || function(arg) {
'use strict';
return arg;
};
/**
* Freezes the given object, but only in debug mode (and in browsers that
* support it). Note that this is a shallow freeze, so for deeply nested
* objects it must be called at every level to ensure deep immutability.
* @param {T} arg
* @return {T}
* @template T
*/
goog.debug.freeze = function(arg) {
'use strict';
// NOTE: this compiles to nothing, but hides the possible side effect of
// freezeInternal_ from the compiler so that the entire call can be
// removed if the result is not used.
return {
valueOf: function() {
'use strict';
return goog.debug.freezeInternal_(arg);
}
}.valueOf();
};
+158
View File
@@ -0,0 +1,158 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @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.
*/
goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');
goog.require('goog.asserts');
/**
* @interface
*/
goog.debug.entryPointRegistry.EntryPointMonitor = function() {};
/**
* Instruments a function.
*
* @param {!Function} fn A function to instrument.
* @return {!Function} The instrumented function.
*/
goog.debug.entryPointRegistry.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 `fn` if it was not
* a wrapped function created by this monitor.
*/
goog.debug.entryPointRegistry.EntryPointMonitor.prototype.unwrap;
/**
* Alias for goog.debug.entryPointRegistry.EntryPointMonitor, for compatibility
* purposes.
* @const
*/
goog.debug.EntryPointMonitor = goog.debug.entryPointRegistry.EntryPointMonitor;
/**
* 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) {
'use strict';
// 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) {
'use strict';
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) {
'use strict';
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--;
};
+72
View File
@@ -0,0 +1,72 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Provides a base class for custom Error objects such that the
* stack is correctly maintained.
*
* You should never need to throw DebugError(msg) directly, Error(msg) is
* sufficient.
*/
goog.module('goog.debug.Error');
goog.module.declareLegacyNamespace();
/**
* Base class for custom error objects.
* @param {*=} msg The message associated with the error.
* @param {{
* message: (?|undefined),
* name: (?|undefined),
* lineNumber: (?|undefined),
* fileName: (?|undefined),
* stack: (?|undefined),
* cause: (?|undefined),
* }=} cause The original error object to chain with.
* @constructor
* @extends {Error}
*/
function DebugError(msg = undefined, cause = undefined) {
// Attempt to ensure there is a stack trace.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DebugError);
} else {
const stack = new Error().stack;
if (stack) {
/** @override */
this.stack = stack;
}
}
if (msg) {
/** @override */
this.message = String(msg);
}
if (cause !== undefined) {
/** @type {?} */
this.cause = cause;
}
/**
* Whether to report this error to the server. Setting this to false will
* cause the error reporter to not report the error back to the server,
* which can be useful if the client knows that the error has already been
* logged on the server.
* @type {boolean}
*/
this.reportErrorToServer = true;
}
goog.inherits(DebugError, Error);
/** @override @type {string} */
DebugError.prototype.name = 'CustomError';
exports = DebugError;
+43
View File
@@ -0,0 +1,43 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Provides methods dealing with context on error objects.
*/
goog.provide('goog.debug.errorcontext');
/**
* Adds key-value context to the error.
* @param {!Error} err The error to add context to.
* @param {string} contextKey Key for the context to be added.
* @param {string} contextValue Value for the context to be added.
*/
goog.debug.errorcontext.addErrorContext = function(
err, contextKey, contextValue) {
'use strict';
if (!err[goog.debug.errorcontext.CONTEXT_KEY_]) {
err[goog.debug.errorcontext.CONTEXT_KEY_] = {};
}
err[goog.debug.errorcontext.CONTEXT_KEY_][contextKey] = contextValue;
};
/**
* @param {!Error} err The error to get context from.
* @return {!Object<string, string>} The context of the provided error.
*/
goog.debug.errorcontext.getErrorContext = function(err) {
'use strict';
return err[goog.debug.errorcontext.CONTEXT_KEY_] || {};
};
// TODO(user): convert this to a Symbol once goog.debug.ErrorReporter is
// able to use ES6.
/** @private @const {string} */
goog.debug.errorcontext.CONTEXT_KEY_ = '__closure__error__context__984382';
+364
View File
@@ -0,0 +1,364 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @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.EntryPointMonitor');
goog.require('goog.debug.Error');
/**
* 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) {
'use strict';
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(malteubl) 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);
/** @override */
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
'use strict';
return this.protectEntryPoint(goog.asserts.assertFunction(fn));
};
/** @override */
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
'use strict';
goog.asserts.assertFunction(fn);
return fn[this.getFunctionIndex_(false)] || fn;
};
/**
* 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) {
'use strict';
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) {
'use strict';
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 overridden 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) {
'use strict';
var that = this;
var googDebugErrorHandlerProtectedFunction = function() {
'use strict';
var self = /** @type {?} */ (this);
if (that.isDisposed()) {
return fn.apply(self, arguments);
}
try {
return fn.apply(self, arguments);
} catch (e) {
that.handleError_(e);
}
};
googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
return googDebugErrorHandlerProtectedFunction;
};
/**
* Internal error handler.
* @param {?} e The error string or an Error-like object.
* @private
*/
goog.debug.ErrorHandler.prototype.handleError_ = function(e) {
'use strict';
// Don't re-report errors that have already been handled by this code.
var MESSAGE_PREFIX =
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX;
if ((e && typeof e === 'object' && typeof e.message === 'string' &&
e.message.indexOf(MESSAGE_PREFIX) == 0) ||
(typeof e === 'string' && e.indexOf(MESSAGE_PREFIX) == 0)) {
return;
}
this.errorHandlerFn_(e);
if (!this.wrapErrors_) {
// Add the prefix to the existing message.
if (this.prefixErrorMessages_) {
if (typeof e === 'object' && e && typeof e.message === 'string') {
/** @type {{message}} */ (e).message = MESSAGE_PREFIX + e.message;
} else {
e = 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 && typeof e.stack === 'string' && 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);
};
// 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() {
'use strict';
this.protectWindowFunctionsHelper_('setTimeout');
};
/**
* Install exception protection for window.setInterval to handle exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowSetInterval = function() {
'use strict';
this.protectWindowFunctionsHelper_('setInterval');
};
/**
* Install an unhandledrejection event listener that reports rejected promises.
* Note: this will only work with Chrome 49+ and friends, but so far is the only
* way to report uncaught errors in aysnc/await functions.
* @param {!Window=} win the window to instrument, defaults to current window
*/
goog.debug.ErrorHandler.prototype.catchUnhandledRejections = function(win) {
'use strict';
win = win || goog.global['window'];
if ('onunhandledrejection' in win) {
win.onunhandledrejection = (event) => {
// event.reason contains the rejection reason. When an Error is
// thrown, this is the Error object. If it is undefined, create a new
// error object.
const e =
event && event.reason ? event.reason : new Error('uncaught error');
this.handleError_(e);
};
}
};
/**
* Install exception protection for window.requestAnimationFrame to handle
* exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =
function() {
'use strict';
var win = goog.global['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) {
'use strict';
var win = goog.global['window'];
var originalFn = win[fnName];
var that = this;
win[fnName] = function(fn, time) {
'use strict';
// 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 (typeof fn === 'string') {
fn = goog.partial(goog.globalEval, fn);
}
arguments[0] = 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.apply) {
return originalFn.apply(/** @type {?} */ (this), arguments);
} else {
var callback = fn;
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
callback = function() {
'use strict';
fn.apply(/** @type {?} */ (this), args);
};
}
return originalFn(callback, 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) {
'use strict';
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) {
'use strict';
this.prefixErrorMessages_ = prefixErrorMessages;
};
/** @override */
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
'use strict';
// Try to unwrap window.setTimeout and window.setInterval.
var win = goog.global['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) {
'use strict';
/** @suppress {missingProperties} message may not be defined. */
var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
(cause && cause.message ? String(cause.message) : String(cause));
goog.debug.ErrorHandler.ProtectedFunctionError.base(
this, 'constructor', message, /** @type {?} */ (cause));
/** @suppress {missingProperties} stack may not be defined. */
var stack = cause && cause.stack;
if (stack && typeof stack === 'string') {
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: ';