initial commit
This commit is contained in:
Executable
+1793
File diff suppressed because it is too large
Load Diff
Executable
+471
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities to check the preconditions, postconditions and
|
||||
* invariants runtime.
|
||||
*
|
||||
* Methods in this package are given special treatment by the compiler
|
||||
* for type-inference. For example, <code>goog.asserts.assert(foo)</code>
|
||||
* will make the compiler treat <code>foo</code> as non-nullable. Similarly,
|
||||
* <code>goog.asserts.assertNumber(foo)</code> informs the compiler about the
|
||||
* type of <code>foo</code>. Where applicable, such assertions are preferable to
|
||||
* casts by jsdoc with <code>@type</code>.
|
||||
*
|
||||
* The compiler has an option to disable asserts. So code like:
|
||||
* <code>
|
||||
* var x = goog.asserts.assert(foo());
|
||||
* goog.asserts.assert(bar());
|
||||
* </code>
|
||||
* will be transformed into:
|
||||
* <code>
|
||||
* var x = foo();
|
||||
* </code>
|
||||
* The compiler will leave in foo() (because its return value is used),
|
||||
* but it will remove bar() because it assumes it does not have side-effects.
|
||||
*
|
||||
* Additionally, note the compiler will consider the type to be "tightened" for
|
||||
* all statements <em>after</em> the assertion. For example:
|
||||
* <code>
|
||||
* const /** ?Object &#ast;/ value = foo();
|
||||
* goog.asserts.assert(value);
|
||||
* // "value" is of type {!Object} at this point.
|
||||
* </code>
|
||||
*/
|
||||
|
||||
goog.provide('goog.asserts');
|
||||
goog.provide('goog.asserts.AssertionError');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.dom.NodeType');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to strip out asserts or to leave them in.
|
||||
*/
|
||||
goog.asserts.ENABLE_ASSERTS =
|
||||
goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error object for failed assertions.
|
||||
* @param {string} messagePattern The pattern that was used to form message.
|
||||
* @param {!Array<*>} messageArgs The items to substitute into the pattern.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
* @final
|
||||
*/
|
||||
goog.asserts.AssertionError = function(messagePattern, messageArgs) {
|
||||
'use strict';
|
||||
goog.debug.Error.call(this, goog.asserts.subs_(messagePattern, messageArgs));
|
||||
|
||||
/**
|
||||
* The message pattern used to format the error message. Error handlers can
|
||||
* use this to uniquely identify the assertion.
|
||||
* @type {string}
|
||||
*/
|
||||
this.messagePattern = messagePattern;
|
||||
};
|
||||
goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
|
||||
|
||||
|
||||
/** @override @type {string} */
|
||||
goog.asserts.AssertionError.prototype.name = 'AssertionError';
|
||||
|
||||
|
||||
/**
|
||||
* The default error handler.
|
||||
* @param {!goog.asserts.AssertionError} e The exception to be handled.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.asserts.DEFAULT_ERROR_HANDLER = function(e) {
|
||||
'use strict';
|
||||
throw e;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The handler responsible for throwing or logging assertion errors.
|
||||
* @private {function(!goog.asserts.AssertionError)}
|
||||
*/
|
||||
goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
|
||||
|
||||
|
||||
/**
|
||||
* Does simple python-style string substitution.
|
||||
* subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
|
||||
* @param {string} pattern The string containing the pattern.
|
||||
* @param {!Array<*>} subs The items to substitute into the pattern.
|
||||
* @return {string} A copy of `str` in which each occurrence of
|
||||
* {@code %s} has been replaced an argument from `var_args`.
|
||||
* @private
|
||||
*/
|
||||
goog.asserts.subs_ = function(pattern, subs) {
|
||||
'use strict';
|
||||
var splitParts = pattern.split('%s');
|
||||
var returnString = '';
|
||||
|
||||
// Replace up to the last split part. We are inserting in the
|
||||
// positions between split parts.
|
||||
var subLast = splitParts.length - 1;
|
||||
for (var i = 0; i < subLast; i++) {
|
||||
// keep unsupplied as '%s'
|
||||
var sub = (i < subs.length) ? subs[i] : '%s';
|
||||
returnString += splitParts[i] + sub;
|
||||
}
|
||||
return returnString + splitParts[subLast];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Throws an exception with the given message and "Assertion failed" prefixed
|
||||
* onto it.
|
||||
* @param {string} defaultMessage The message to use if givenMessage is empty.
|
||||
* @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
|
||||
* @param {string|undefined} givenMessage Message supplied by the caller.
|
||||
* @param {Array<*>} givenArgs The substitution arguments for givenMessage.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a number.
|
||||
* @private
|
||||
*/
|
||||
goog.asserts.doAssertFailure_ = function(
|
||||
defaultMessage, defaultArgs, givenMessage, givenArgs) {
|
||||
'use strict';
|
||||
var message = 'Assertion failed';
|
||||
if (givenMessage) {
|
||||
message += ': ' + givenMessage;
|
||||
var args = givenArgs;
|
||||
} else if (defaultMessage) {
|
||||
message += ': ' + defaultMessage;
|
||||
args = defaultArgs;
|
||||
}
|
||||
// The '' + works around an Opera 10 bug in the unit tests. Without it,
|
||||
// a stack trace is added to var message above. With this, a stack trace is
|
||||
// not added until this line (it causes the extra garbage to be added after
|
||||
// the assertion message instead of in the middle of it).
|
||||
var e = new goog.asserts.AssertionError('' + message, args || []);
|
||||
goog.asserts.errorHandler_(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a custom error handler that can be used to customize the behavior of
|
||||
* assertion failures, for example by turning all assertion failures into log
|
||||
* messages.
|
||||
* @param {function(!goog.asserts.AssertionError)} errorHandler
|
||||
* @return {void}
|
||||
*/
|
||||
goog.asserts.setErrorHandler = function(errorHandler) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS) {
|
||||
goog.asserts.errorHandler_ = errorHandler;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
|
||||
* true.
|
||||
* @template T
|
||||
* @param {T} condition The condition to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {T} The value of the condition.
|
||||
* @throws {goog.asserts.AssertionError} When the condition evaluates to false.
|
||||
* @closurePrimitive {asserts.truthy}
|
||||
*/
|
||||
goog.asserts.assert = function(condition, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && !condition) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'', null, opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return condition;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if `value` is `null` or `undefined` if goog.asserts.ENABLE_ASSERTS is
|
||||
* true.
|
||||
*
|
||||
* @param {T} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {R} `value` with its type narrowed to exclude `null` and `undefined`.
|
||||
*
|
||||
* @template T
|
||||
* @template R :=
|
||||
* mapunion(T, (V) =>
|
||||
* cond(eq(V, 'null'),
|
||||
* none(),
|
||||
* cond(eq(V, 'undefined'),
|
||||
* none(),
|
||||
* V)))
|
||||
* =:
|
||||
*
|
||||
* @throws {!goog.asserts.AssertionError} When `value` is `null` or `undefined`.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertExists = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && value == null) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected to exist: %s.', [value], opt_message,
|
||||
Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
|
||||
* when we want to add a check in the unreachable area like switch-case
|
||||
* statement:
|
||||
*
|
||||
* <pre>
|
||||
* switch(type) {
|
||||
* case FOO: doSomething(); break;
|
||||
* case BAR: doSomethingElse(); break;
|
||||
* default: goog.asserts.fail('Unrecognized type: ' + type);
|
||||
* // We have only 2 types - "default:" section is unreachable code.
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {void}
|
||||
* @throws {goog.asserts.AssertionError} Failure.
|
||||
* @closurePrimitive {asserts.fail}
|
||||
*/
|
||||
goog.asserts.fail = function(opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS) {
|
||||
goog.asserts.errorHandler_(new goog.asserts.AssertionError(
|
||||
'Failure' + (opt_message ? ': ' + opt_message : ''),
|
||||
Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {number} The value, guaranteed to be a number when asserts enabled.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a number.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertNumber = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && typeof value !== 'number') {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected number but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {number} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {string} The value, guaranteed to be a string when asserts enabled.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a string.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertString = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && typeof value !== 'string') {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected string but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {string} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {!Function} The value, guaranteed to be a function when asserts
|
||||
* enabled.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a function.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertFunction = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && typeof value !== 'function') {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected function but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {!Function} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {!Object} The value, guaranteed to be a non-null object.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not an object.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertObject = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected object but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {!Object} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {!Array<?>} The value, guaranteed to be a non-null array.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not an array.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertArray = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && !Array.isArray(value)) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected array but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {!Array<?>} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {boolean} The value, guaranteed to be a boolean when asserts are
|
||||
* enabled.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a boolean.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && typeof value !== 'boolean') {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected boolean but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {boolean} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @return {!Element} The value, likely to be a DOM Element when asserts are
|
||||
* enabled.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not an Element.
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
* @deprecated Use goog.asserts.dom.assertIsElement instead.
|
||||
*/
|
||||
goog.asserts.assertElement = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS &&
|
||||
(!goog.isObject(value) ||
|
||||
/** @type {!Node} */ (value).nodeType != goog.dom.NodeType.ELEMENT)) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected Element but got %s: %s.', [goog.typeOf(value), value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {!Element} */ (value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is an instance of the user-defined type if
|
||||
* goog.asserts.ENABLE_ASSERTS is true.
|
||||
*
|
||||
* The compiler may tighten the type returned by this function.
|
||||
*
|
||||
* Do not use this to ensure a value is an HTMLElement or a subclass! Cross-
|
||||
* document DOM inherits from separate - though identical - browser classes, and
|
||||
* such a check will unexpectedly fail. Please use the methods in
|
||||
* goog.asserts.dom for these purposes.
|
||||
*
|
||||
* @param {?} value The value to check.
|
||||
* @param {function(new: T, ...)} type A user-defined constructor.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not an instance of
|
||||
* type.
|
||||
* @return {T}
|
||||
* @template T
|
||||
* @closurePrimitive {asserts.matchesReturn}
|
||||
*/
|
||||
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected instanceof %s but got %s.',
|
||||
[goog.asserts.getType_(type), goog.asserts.getType_(value)],
|
||||
opt_message, Array.prototype.slice.call(arguments, 3));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the value is a finite number, if goog.asserts.ENABLE_ASSERTS
|
||||
* is true.
|
||||
*
|
||||
* @param {*} value The value to check.
|
||||
* @param {string=} opt_message Error message in case of failure.
|
||||
* @param {...*} var_args The items to substitute into the failure message.
|
||||
* @throws {goog.asserts.AssertionError} When the value is not a number, or is
|
||||
* a non-finite number such as NaN, Infinity or -Infinity.
|
||||
* @return {number} The value initially passed in.
|
||||
*/
|
||||
goog.asserts.assertFinite = function(value, opt_message, var_args) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS &&
|
||||
(typeof value != 'number' || !isFinite(value))) {
|
||||
goog.asserts.doAssertFailure_(
|
||||
'Expected %s to be a finite number but it is not.', [value],
|
||||
opt_message, Array.prototype.slice.call(arguments, 2));
|
||||
}
|
||||
return /** @type {number} */ (value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the type of a value. If a constructor is passed, and a suitable
|
||||
* string cannot be found, 'unknown type name' will be returned.
|
||||
* @param {*} value A constructor, object, or primitive.
|
||||
* @return {string} The best display name for the value, or 'unknown type name'.
|
||||
* @private
|
||||
*/
|
||||
goog.asserts.getType_ = 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;
|
||||
}
|
||||
};
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Simple freelist.
|
||||
*
|
||||
* An anterative to goog.structs.SimplePool, it imposes the requirement that the
|
||||
* objects in the list contain a "next" property that can be used to maintain
|
||||
* the pool.
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.FreeList');
|
||||
|
||||
|
||||
/**
|
||||
* @template ITEM
|
||||
*/
|
||||
goog.async.FreeList = class {
|
||||
/**
|
||||
* @param {function():ITEM} create
|
||||
* @param {function(ITEM):void} reset
|
||||
* @param {number} limit
|
||||
*/
|
||||
constructor(create, reset, limit) {
|
||||
/** @private @const {number} */
|
||||
this.limit_ = limit;
|
||||
/** @private @const {function()} */
|
||||
this.create_ = create;
|
||||
/** @private @const {function(ITEM):void} */
|
||||
this.reset_ = reset;
|
||||
|
||||
/** @private {number} */
|
||||
this.occupants_ = 0;
|
||||
/** @private {ITEM} */
|
||||
this.head_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {ITEM}
|
||||
*/
|
||||
get() {
|
||||
let item;
|
||||
if (this.occupants_ > 0) {
|
||||
this.occupants_--;
|
||||
item = this.head_;
|
||||
this.head_ = item.next;
|
||||
item.next = null;
|
||||
} else {
|
||||
item = this.create_();
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ITEM} item An item available for possible future reuse.
|
||||
*/
|
||||
put(item) {
|
||||
this.reset_(item);
|
||||
if (this.occupants_ < this.limit_) {
|
||||
this.occupants_++;
|
||||
item.next = this.head_;
|
||||
this.head_ = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible for testing.
|
||||
* @package
|
||||
* @return {number}
|
||||
*/
|
||||
occupants() {
|
||||
return this.occupants_;
|
||||
}
|
||||
};
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a function to schedule running a function as soon
|
||||
* as possible after the current JS execution stops and yields to the event
|
||||
* loop.
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.nextTick');
|
||||
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.labs.userAgent.browser');
|
||||
goog.require('goog.labs.userAgent.engine');
|
||||
|
||||
|
||||
/**
|
||||
* Fires the provided callbacks as soon as possible after the current JS
|
||||
* execution context. setTimeout(…, 0) takes at least 4ms when called from
|
||||
* within another setTimeout(…, 0) for legacy reasons.
|
||||
*
|
||||
* This will not schedule the callback as a microtask (i.e. a task that can
|
||||
* preempt user input or networking callbacks). It is meant to emulate what
|
||||
* setTimeout(_, 0) would do if it were not throttled. If you desire microtask
|
||||
* behavior, use {@see goog.Promise} instead.
|
||||
*
|
||||
* @param {function(this:SCOPE)} callback Callback function to fire as soon as
|
||||
* possible.
|
||||
* @param {SCOPE=} opt_context Object in whose scope to call the listener.
|
||||
* @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
|
||||
* ensures correctness at the cost of speed. See comments for details.
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
|
||||
'use strict';
|
||||
var cb = callback;
|
||||
if (opt_context) {
|
||||
cb = goog.bind(callback, opt_context);
|
||||
}
|
||||
cb = goog.async.nextTick.wrapCallback_(cb);
|
||||
// Note we do allow callers to also request setImmediate if they are willing
|
||||
// to accept the possible tradeoffs of incorrectness in exchange for speed.
|
||||
// The IE fallback of readystate change is much slower. See useSetImmediate_
|
||||
// for details.
|
||||
if (typeof goog.global.setImmediate === 'function' &&
|
||||
(opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) {
|
||||
goog.global.setImmediate(cb);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for and cache the custom fallback version of setImmediate.
|
||||
if (!goog.async.nextTick.setImmediate_) {
|
||||
goog.async.nextTick.setImmediate_ =
|
||||
goog.async.nextTick.getSetImmediateEmulator_();
|
||||
}
|
||||
goog.async.nextTick.setImmediate_(cb);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether should use setImmediate implementation currently on window.
|
||||
*
|
||||
* window.setImmediate was introduced and currently only supported by IE10+,
|
||||
* but due to a bug in the implementation it is not guaranteed that
|
||||
* setImmediate is faster than setTimeout nor that setImmediate N is before
|
||||
* setImmediate N+1. That is why we do not use the native version if
|
||||
* available. We do, however, call setImmediate if it is a non-native function
|
||||
* because that indicates that it has been replaced by goog.testing.MockClock
|
||||
* which we do want to support.
|
||||
* See
|
||||
* http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
|
||||
*
|
||||
* @return {boolean} Whether to use the implementation of setImmediate defined
|
||||
* on Window.
|
||||
* @private
|
||||
* @suppress {missingProperties} For "Window.prototype.setImmediate"
|
||||
*/
|
||||
goog.async.nextTick.useSetImmediate_ = function() {
|
||||
'use strict';
|
||||
// Not a browser environment.
|
||||
if (!goog.global.Window || !goog.global.Window.prototype) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// MS Edge has window.setImmediate natively, but it's not on Window.prototype.
|
||||
// Also, there's no clean way to detect if the goog.global.setImmediate has
|
||||
// been replaced by mockClock as its replacement also shows up as "[native
|
||||
// code]" when using toString. Therefore, just always use
|
||||
// goog.global.setImmediate for Edge. It's unclear if it suffers the same
|
||||
// issues as IE10/11, but based on
|
||||
// https://dev.modern.ie/testdrive/demos/setimmediatesorting/
|
||||
// it seems they've been working to ensure it's WAI.
|
||||
if (goog.labs.userAgent.browser.isEdge() ||
|
||||
goog.global.Window.prototype.setImmediate != goog.global.setImmediate) {
|
||||
// Something redefined setImmediate in which case we decide to use it (This
|
||||
// is so that we use the mockClock setImmediate).
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cache for the setImmediate implementation.
|
||||
* @type {function(function())}
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.setImmediate_;
|
||||
|
||||
|
||||
/**
|
||||
* Determines the best possible implementation to run a function as soon as
|
||||
* the JS event loop is idle.
|
||||
* @return {function(function())} The "setImmediate" implementation.
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.getSetImmediateEmulator_ = function() {
|
||||
'use strict';
|
||||
// Create a private message channel and use it to postMessage empty messages
|
||||
// to ourselves.
|
||||
/** @type {!Function|undefined} */
|
||||
var Channel = goog.global['MessageChannel'];
|
||||
// If MessageChannel is not available and we are in a browser, implement
|
||||
// an iframe based polyfill in browsers that have postMessage and
|
||||
// document.addEventListener. The latter excludes IE8 because it has a
|
||||
// synchronous postMessage implementation.
|
||||
if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
|
||||
window.postMessage && window.addEventListener &&
|
||||
// Presto (The old pre-blink Opera engine) has problems with iframes
|
||||
// and contentWindow.
|
||||
!goog.labs.userAgent.engine.isPresto()) {
|
||||
/** @constructor */
|
||||
Channel = function() {
|
||||
'use strict';
|
||||
// Make an empty, invisible iframe.
|
||||
var iframe = goog.dom.createElement(goog.dom.TagName.IFRAME);
|
||||
iframe.style.display = 'none';
|
||||
document.documentElement.appendChild(iframe);
|
||||
var win = iframe.contentWindow;
|
||||
var doc = win.document;
|
||||
doc.open();
|
||||
doc.close();
|
||||
// Do not post anything sensitive over this channel, as the workaround for
|
||||
// pages with file: origin could allow that information to be modified or
|
||||
// intercepted.
|
||||
var message = 'callImmediate' + Math.random();
|
||||
// The same origin policy rejects attempts to postMessage from file: urls
|
||||
// unless the origin is '*'.
|
||||
var origin = win.location.protocol == 'file:' ?
|
||||
'*' :
|
||||
win.location.protocol + '//' + win.location.host;
|
||||
var onmessage = goog.bind(function(e) {
|
||||
'use strict';
|
||||
// Validate origin and message to make sure that this message was
|
||||
// intended for us. If the origin is set to '*' (see above) only the
|
||||
// message needs to match since, for example, '*' != 'file://'. Allowing
|
||||
// the wildcard is ok, as we are not concerned with security here.
|
||||
if ((origin != '*' && e.origin != origin) || e.data != message) {
|
||||
return;
|
||||
}
|
||||
this['port1'].onmessage();
|
||||
}, this);
|
||||
win.addEventListener('message', onmessage, false);
|
||||
this['port1'] = {};
|
||||
this['port2'] = {
|
||||
postMessage: function() {
|
||||
'use strict';
|
||||
win.postMessage(message, origin);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
if (typeof Channel !== 'undefined' && !goog.labs.userAgent.browser.isIE()) {
|
||||
// Exclude all of IE due to
|
||||
// http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
|
||||
// which allows starving postMessage with a busy setTimeout loop.
|
||||
// This currently affects IE10 and IE11 which would otherwise be able
|
||||
// to use the postMessage based fallbacks.
|
||||
var channel = new Channel();
|
||||
// Use a fifo linked list to call callbacks in the right order.
|
||||
var head = {};
|
||||
var tail = head;
|
||||
channel['port1'].onmessage = function() {
|
||||
'use strict';
|
||||
if (head.next !== undefined) {
|
||||
head = head.next;
|
||||
var cb = head.cb;
|
||||
head.cb = null;
|
||||
cb();
|
||||
}
|
||||
};
|
||||
return function(cb) {
|
||||
'use strict';
|
||||
tail.next = {cb: cb};
|
||||
tail = tail.next;
|
||||
channel['port2'].postMessage(0);
|
||||
};
|
||||
}
|
||||
// Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
|
||||
// or more.
|
||||
// NOTE(user): This fallback is used for IE.
|
||||
return function(cb) {
|
||||
'use strict';
|
||||
goog.global.setTimeout(/** @type {function()} */ (cb), 0);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function that is overrided to protect callbacks with entry point
|
||||
* monitor if the application monitors entry points.
|
||||
* @param {function()} callback Callback function to fire as soon as possible.
|
||||
* @return {function()} The wrapped callback.
|
||||
* @private
|
||||
*/
|
||||
goog.async.nextTick.wrapCallback_ = goog.functions.identity;
|
||||
|
||||
|
||||
// Register the callback function as an entry point, so that it can be
|
||||
// monitored for exception handling, etc. This has to be done in this file
|
||||
// since it requires special code to handle all browsers.
|
||||
goog.debug.entryPointRegistry.register(
|
||||
/**
|
||||
* @param {function(!Function): !Function} transformer The transforming
|
||||
* function.
|
||||
*/
|
||||
function(transformer) {
|
||||
'use strict';
|
||||
goog.async.nextTick.wrapCallback_ = transformer;
|
||||
});
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.run');
|
||||
|
||||
goog.require('goog.async.WorkQueue');
|
||||
goog.require('goog.async.nextTick');
|
||||
goog.require('goog.async.throwException');
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, use the global Promise to implement goog.async.run
|
||||
* assuming either the native, or polyfill version will be used. Does still
|
||||
* permit tests to use forceNextTick.
|
||||
*/
|
||||
goog.ASSUME_NATIVE_PROMISE = goog.define('goog.ASSUME_NATIVE_PROMISE', false);
|
||||
|
||||
/**
|
||||
* Fires the provided callback just before the current callstack unwinds, or as
|
||||
* soon as possible after the current JS execution context.
|
||||
* @param {function(this:THIS)} callback
|
||||
* @param {THIS=} opt_context Object to use as the "this value" when calling
|
||||
* the provided function.
|
||||
* @template THIS
|
||||
*/
|
||||
goog.async.run = function(callback, opt_context) {
|
||||
'use strict';
|
||||
if (!goog.async.run.schedule_) {
|
||||
goog.async.run.initializeRunner_();
|
||||
}
|
||||
if (!goog.async.run.workQueueScheduled_) {
|
||||
// Nothing is currently scheduled, schedule it now.
|
||||
goog.async.run.schedule_();
|
||||
goog.async.run.workQueueScheduled_ = true;
|
||||
}
|
||||
|
||||
goog.async.run.workQueue_.add(callback, opt_context);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the function to use to process the work queue.
|
||||
* @private
|
||||
*/
|
||||
goog.async.run.initializeRunner_ = function() {
|
||||
'use strict';
|
||||
if (goog.ASSUME_NATIVE_PROMISE ||
|
||||
(goog.global.Promise && goog.global.Promise.resolve)) {
|
||||
// Use goog.global.Promise instead of just Promise because the relevant
|
||||
// externs may be missing, and don't alias it because this could confuse the
|
||||
// compiler into thinking the polyfill is required when it should be treated
|
||||
// as optional.
|
||||
var promise = goog.global.Promise.resolve(undefined);
|
||||
goog.async.run.schedule_ = function() {
|
||||
'use strict';
|
||||
promise.then(goog.async.run.processWorkQueue);
|
||||
};
|
||||
} else {
|
||||
goog.async.run.schedule_ = function() {
|
||||
'use strict';
|
||||
goog.async.nextTick(goog.async.run.processWorkQueue);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Forces goog.async.run to use nextTick instead of Promise.
|
||||
*
|
||||
* This should only be done in unit tests. It's useful because MockClock
|
||||
* replaces nextTick, but not the browser Promise implementation, so it allows
|
||||
* Promise-based code to be tested with MockClock.
|
||||
*
|
||||
* However, we also want to run promises if the MockClock is no longer in
|
||||
* control so we schedule a backup "setTimeout" to the unmocked timeout if
|
||||
* provided.
|
||||
*
|
||||
* @param {function(function())=} opt_realSetTimeout
|
||||
*/
|
||||
goog.async.run.forceNextTick = function(opt_realSetTimeout) {
|
||||
'use strict';
|
||||
goog.async.run.schedule_ = function() {
|
||||
'use strict';
|
||||
goog.async.nextTick(goog.async.run.processWorkQueue);
|
||||
if (opt_realSetTimeout) {
|
||||
opt_realSetTimeout(goog.async.run.processWorkQueue);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The function used to schedule work asynchronousely.
|
||||
* @private {function()}
|
||||
*/
|
||||
goog.async.run.schedule_;
|
||||
|
||||
|
||||
/** @private {boolean} */
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
|
||||
|
||||
/** @private {!goog.async.WorkQueue} */
|
||||
goog.async.run.workQueue_ = new goog.async.WorkQueue();
|
||||
|
||||
|
||||
if (goog.DEBUG) {
|
||||
/**
|
||||
* Reset the work queue. Only available for tests in debug mode.
|
||||
*/
|
||||
goog.async.run.resetQueue = function() {
|
||||
'use strict';
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
goog.async.run.workQueue_ = new goog.async.WorkQueue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the scheduler. Only available for tests in debug mode.
|
||||
*/
|
||||
goog.async.run.resetSchedulerForTest = function() {
|
||||
goog.async.run.initializeRunner_();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run any pending goog.async.run work items. This function is not intended
|
||||
* for general use, but for use by entry point handlers to run items ahead of
|
||||
* goog.async.nextTick.
|
||||
*/
|
||||
goog.async.run.processWorkQueue = function() {
|
||||
'use strict';
|
||||
// NOTE: additional work queue items may be added while processing.
|
||||
var item = null;
|
||||
while (item = goog.async.run.workQueue_.remove()) {
|
||||
try {
|
||||
item.fn.call(item.scope);
|
||||
} catch (e) {
|
||||
goog.async.throwException(e);
|
||||
}
|
||||
goog.async.run.workQueue_.returnUnused(item);
|
||||
}
|
||||
|
||||
// There are no more work items, allow processing to be scheduled again.
|
||||
goog.async.run.workQueueScheduled_ = false;
|
||||
};
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Provides a function to throw an error without interrupting
|
||||
* the current execution context.
|
||||
*/
|
||||
|
||||
goog.module('goog.async.throwException');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
/**
|
||||
* Throw an item without interrupting the current execution context. For
|
||||
* example, if processing a group of items in a loop, sometimes it is useful
|
||||
* to report an error while still allowing the rest of the batch to be
|
||||
* processed.
|
||||
* @param {*} exception
|
||||
*/
|
||||
function throwException(exception) {
|
||||
// Each throw needs to be in its own context.
|
||||
goog.global.setTimeout(() => {
|
||||
throw exception;
|
||||
}, 0);
|
||||
}
|
||||
exports = throwException;
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.module('goog.async.WorkQueue');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const FreeList = goog.require('goog.async.FreeList');
|
||||
const {assert} = goog.require('goog.asserts');
|
||||
|
||||
// TODO(johnlenz): generalize the WorkQueue if this is used by more
|
||||
// than goog.async.run.
|
||||
|
||||
|
||||
/**
|
||||
* A low GC workqueue. The key elements of this design:
|
||||
* - avoids the need for goog.bind or equivalent by carrying scope
|
||||
* - avoids the need for array reallocation by using a linked list
|
||||
* - minimizes work entry objects allocation by recycling objects
|
||||
* @final
|
||||
* @struct
|
||||
*/
|
||||
class WorkQueue {
|
||||
constructor() {
|
||||
this.workHead_ = null;
|
||||
this.workTail_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()} fn
|
||||
* @param {Object|null|undefined} scope
|
||||
*/
|
||||
add(fn, scope) {
|
||||
const item = this.getUnusedItem_();
|
||||
item.set(fn, scope);
|
||||
|
||||
if (this.workTail_) {
|
||||
this.workTail_.next = item;
|
||||
this.workTail_ = item;
|
||||
} else {
|
||||
assert(!this.workHead_);
|
||||
this.workHead_ = item;
|
||||
this.workTail_ = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {?WorkItem}
|
||||
*/
|
||||
remove() {
|
||||
let item = null;
|
||||
|
||||
if (this.workHead_) {
|
||||
item = this.workHead_;
|
||||
this.workHead_ = this.workHead_.next;
|
||||
if (!this.workHead_) {
|
||||
this.workTail_ = null;
|
||||
}
|
||||
item.next = null;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!WorkItem} item
|
||||
*/
|
||||
returnUnused(item) {
|
||||
WorkQueue.freelist_.put(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!WorkItem}
|
||||
* @private
|
||||
*/
|
||||
getUnusedItem_() {
|
||||
return WorkQueue.freelist_.get();
|
||||
}
|
||||
}
|
||||
|
||||
/** @define {number} The maximum number of entries to keep for recycling. */
|
||||
WorkQueue.DEFAULT_MAX_UNUSED =
|
||||
goog.define('goog.async.WorkQueue.DEFAULT_MAX_UNUSED', 100);
|
||||
|
||||
/** @const @private {!FreeList<!WorkItem>} */
|
||||
WorkQueue.freelist_ = new FreeList(
|
||||
() => new WorkItem(), item => item.reset(), WorkQueue.DEFAULT_MAX_UNUSED);
|
||||
|
||||
/**
|
||||
* @final
|
||||
* @struct
|
||||
*/
|
||||
class WorkItem {
|
||||
constructor() {
|
||||
/** @type {?function()} */
|
||||
this.fn = null;
|
||||
/** @type {?Object|null|undefined} */
|
||||
this.scope = null;
|
||||
/** @type {?WorkItem} */
|
||||
this.next = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function()} fn
|
||||
* @param {Object|null|undefined} scope
|
||||
*/
|
||||
set(fn, scope) {
|
||||
'use strict';
|
||||
this.fn = fn;
|
||||
this.scope = scope;
|
||||
this.next = null;
|
||||
}
|
||||
|
||||
/** Reset the work item so they don't prevent GC before reuse */
|
||||
reset() {
|
||||
this.fn = null;
|
||||
this.scope = null;
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
exports = WorkQueue;
|
||||
Executable
+3889
File diff suppressed because it is too large
Load Diff
Executable
+222
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for working with ES6 iterables.
|
||||
*
|
||||
* The goal is that this should be a replacement for goog.iter which uses
|
||||
* a now non-standard approach to iterables.
|
||||
*
|
||||
* @see https://goo.gl/Rok5YQ
|
||||
*/
|
||||
|
||||
goog.module('goog.collections.iters');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
/**
|
||||
* Get the iterator for an iterable.
|
||||
* @param {!Iterable<VALUE>} iterable
|
||||
* @return {!Iterator<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
function getIterator(iterable) {
|
||||
return iterable[goog.global.Symbol.iterator]();
|
||||
}
|
||||
exports.getIterator = getIterator;
|
||||
|
||||
|
||||
/**
|
||||
* Call a function with every value of an iterable.
|
||||
*
|
||||
* Warning: this function will never halt if given an iterable that
|
||||
* is never exhausted.
|
||||
*
|
||||
* @param {!Iterable<VALUE>} iterable
|
||||
* @param {function(VALUE) : *} f
|
||||
* @template VALUE
|
||||
*/
|
||||
exports.forEach = function(iterable, f) {
|
||||
for (const elem of iterable) {
|
||||
f(elem);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An Iterable that wraps a child iterable, and maps every element of the child
|
||||
* iterator to a new value, using a mapping function. Similar to Array.map, but
|
||||
* for Iterable.
|
||||
* @template TO,FROM
|
||||
* @implements {IteratorIterable<TO>}
|
||||
*/
|
||||
class MapIterator {
|
||||
/**
|
||||
* @param {!Iterable<FROM>} childIter
|
||||
* @param {function(FROM, number): TO} mapFn
|
||||
*/
|
||||
constructor(childIter, mapFn) {
|
||||
/** @private @const {!Iterator<FROM>} */
|
||||
this.childIterator_ = getIterator(childIter);
|
||||
|
||||
/** @private @const {function(FROM, number): TO} */
|
||||
this.mapFn_ = mapFn;
|
||||
|
||||
/** @private {number} */
|
||||
this.nextIndex_ = 0;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
next() {
|
||||
const childResult = this.childIterator_.next();
|
||||
// Always return a new object, even when childResult.done == true. This is
|
||||
// so that we don't accidentally preserve generator return values, which
|
||||
// are unlikely to be meaningful in the context of this MapIterator.
|
||||
return {
|
||||
value: childResult.done ?
|
||||
undefined :
|
||||
this.mapFn_.call(undefined, childResult.value, this.nextIndex_++),
|
||||
done: childResult.done,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps the values of one iterable to create another iterable.
|
||||
*
|
||||
* When next() is called on the returned iterable, it will call the given
|
||||
* function `f` with the next value of the given iterable
|
||||
* `iterable` until the given iterable is exhausted.
|
||||
*
|
||||
* @param {!Iterable<VALUE>} iterable
|
||||
* @param {function(VALUE, number): RESULT} f
|
||||
* @return {!IteratorIterable<RESULT>} The created iterable that gives the
|
||||
* mapped values.
|
||||
* @template VALUE, RESULT
|
||||
*/
|
||||
exports.map = function(iterable, f) {
|
||||
return new MapIterator(iterable, f);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An Iterable that wraps a child Iterable and returns a subset of the child's
|
||||
* items, based on a filter function. Similar to Array.filter, but for
|
||||
* Iterable.
|
||||
* @template T
|
||||
* @implements {IteratorIterable<T>}
|
||||
*/
|
||||
class FilterIterator {
|
||||
/**
|
||||
* @param {!Iterable<T>} childIter
|
||||
* @param {function(T, number): boolean} filterFn
|
||||
*/
|
||||
constructor(childIter, filterFn) {
|
||||
/** @private @const {!Iterator<T>} */
|
||||
this.childIter_ = getIterator(childIter);
|
||||
|
||||
/** @private @const {function(T, number): boolean} */
|
||||
this.filterFn_ = filterFn;
|
||||
|
||||
/** @private {number} */
|
||||
this.nextIndex_ = 0;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
next() {
|
||||
while (true) {
|
||||
const childResult = this.childIter_.next();
|
||||
if (childResult.done) {
|
||||
// Don't return childResult directly, because that would preserve
|
||||
// generator return values, and we want to ignore them.
|
||||
return {done: true, value: undefined};
|
||||
}
|
||||
const passesFilter =
|
||||
this.filterFn_.call(undefined, childResult.value, this.nextIndex_++);
|
||||
if (passesFilter) {
|
||||
return childResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter elements from one iterator to create another iterable.
|
||||
*
|
||||
* When next() is called on the returned iterator, it will call next() on the
|
||||
* given iterator and call the given function `f` with that value until `true`
|
||||
* is returned or the given iterator is exhausted.
|
||||
*
|
||||
* @param {!Iterable<VALUE>} iterable
|
||||
* @param {function(VALUE, number): boolean} f
|
||||
* @return {!IteratorIterable<VALUE>} The created iterable that gives the mapped
|
||||
* values.
|
||||
* @template VALUE
|
||||
*/
|
||||
exports.filter = function(iterable, f) {
|
||||
return new FilterIterator(iterable, f);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @implements {IteratorIterable<T>}
|
||||
*/
|
||||
class ConcatIterator {
|
||||
/** @param {!Array<!Iterator<T>>} iterators */
|
||||
constructor(iterators) {
|
||||
/** @private @const {!Array<!Iterator<T>>} */
|
||||
this.iterators_ = iterators;
|
||||
|
||||
/** @private {number} */
|
||||
this.iterIndex_ = 0;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
next() {
|
||||
while (this.iterIndex_ < this.iterators_.length) {
|
||||
const result = this.iterators_[this.iterIndex_].next();
|
||||
if (!result.done) {
|
||||
return result;
|
||||
}
|
||||
this.iterIndex_++;
|
||||
}
|
||||
return /** @type {!IIterableResult<T>} */ ({done: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates multiple iterators to create a new iterable.
|
||||
*
|
||||
* When next() is called on the return iterator, it will call next() on the
|
||||
* current passed iterator. When the current passed iterator is exhausted, it
|
||||
* will move on to the next iterator until there are no more left.
|
||||
*
|
||||
* All generator return values will be ignored (i.e. when childIter.next()
|
||||
* returns {done: true, value: notUndefined} it will be treated as just
|
||||
* {done: true}).
|
||||
*
|
||||
* @param {...!Iterable<VALUE>} iterables
|
||||
* @return {!IteratorIterable<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
exports.concat = function(...iterables) {
|
||||
return new ConcatIterator(iterables.map(getIterator));
|
||||
};
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Helper methods that operate on Map-like objects (e.g. ES6
|
||||
* Maps).
|
||||
*/
|
||||
|
||||
goog.module('goog.collections.maps');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
/**
|
||||
* A MapLike implements the same public interface as an ES6 Map, without tying
|
||||
* the underlying code directly to the implementation. Any additions to this
|
||||
* type should also be present on ES6 Maps.
|
||||
* @template K,V
|
||||
* @record
|
||||
*/
|
||||
class MapLike {
|
||||
constructor() {
|
||||
/** @const {number} The number of items in this map. */
|
||||
this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {K} key The key to set in the map.
|
||||
* @param {V} val The value to set for the given key in the map.
|
||||
*/
|
||||
set(key, val) {};
|
||||
|
||||
/**
|
||||
* @param {K} key The key to retrieve from the map.
|
||||
* @return {V|undefined} The value for this key, or undefined if the key is
|
||||
* not present in the map.
|
||||
*/
|
||||
get(key) {};
|
||||
|
||||
/**
|
||||
* @return {!IteratorIterable<K>} An ES6 Iterator that iterates over the keys
|
||||
* in the map.
|
||||
*/
|
||||
keys() {};
|
||||
|
||||
/**
|
||||
* @return {!IteratorIterable<V>} An ES6 Iterator that iterates over the
|
||||
* values in the map.
|
||||
*/
|
||||
values() {};
|
||||
|
||||
/**
|
||||
* @param {K} key The key to check.
|
||||
* @return {boolean} True iff this key is present in the map.
|
||||
*/
|
||||
has(key) {};
|
||||
}
|
||||
exports.MapLike = MapLike;
|
||||
|
||||
/**
|
||||
* Iterates over each entry in the given entries and sets the entry in
|
||||
* the map, overwriting any existing entries for the key.
|
||||
* @param {!MapLike<K,V>} map The map to set entries on.
|
||||
* @param {?Iterable<!Array<K|V>>} entries The iterable of entries. This
|
||||
* iterable should really be of type Iterable<Array<[K,V]>>, but the tuple
|
||||
* type is not representable in the Closure Type System.
|
||||
* @template K,V
|
||||
*/
|
||||
function setAll(map, entries) {
|
||||
if (!entries) return;
|
||||
for (const [k, v] of entries) {
|
||||
map.set(k, v);
|
||||
}
|
||||
}
|
||||
exports.setAll = setAll;
|
||||
|
||||
/**
|
||||
* Determines if a given map contains the given value, optionally using
|
||||
* a custom comparison function.
|
||||
* @param {!MapLike<?,V1>} map The map whose values to check.
|
||||
* @param {V2} val The value to check for.
|
||||
* @param {(function(V1,V2): boolean)=} valueEqualityFn The comparison function
|
||||
* used to determine if the given value is equivalent to any of the values
|
||||
* in the map. If no function is provided, defaults to strict equality
|
||||
* (===).
|
||||
* @return {boolean} True iff the given map contains the given value according
|
||||
* to the comparison function.
|
||||
* @template V1,V2
|
||||
*/
|
||||
function hasValue(map, val, valueEqualityFn = defaultEqualityFn) {
|
||||
for (const v of map.values()) {
|
||||
if (valueEqualityFn(v, val)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.hasValue = hasValue;
|
||||
|
||||
/** @const {function(?,?): boolean} */
|
||||
const defaultEqualityFn = (a, b) => a === b;
|
||||
|
||||
/**
|
||||
* Compares two maps using their public APIs to determine if they have
|
||||
* equal contents, optionally using a custom comparison function when comaring
|
||||
* values.
|
||||
* @param {!MapLike<K,V1>} map The first map
|
||||
* @param {!MapLike<K,V2>} otherMap The other map
|
||||
* @param {(function(V1,V2): boolean)=} valueEqualityFn The comparison function
|
||||
* used to determine if the values obtained from each map are equivalent. If
|
||||
* no function is provided, defaults to strict equality (===).
|
||||
* @return {boolean}
|
||||
* @template K,V1,V2
|
||||
*/
|
||||
function equals(map, otherMap, valueEqualityFn = defaultEqualityFn) {
|
||||
if (map === otherMap) return true;
|
||||
if (map.size !== otherMap.size) return false;
|
||||
for (const key of map.keys()) {
|
||||
if (!otherMap.has(key)) return false;
|
||||
if (!valueEqualityFn(map.get(key), otherMap.get(key))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
exports.equals = equals;
|
||||
|
||||
/**
|
||||
* Returns a new ES6 Map in which all the keys and values from the
|
||||
* given map are interchanged (keys become values and values become keys). If
|
||||
* multiple keys in the given map to the same value, the resulting value in the
|
||||
* transposed map is implementation-dependent.
|
||||
*
|
||||
* It acts very similarly to {goog.object.transpose(Object)}.
|
||||
* @param {!MapLike<K,V>} map The map to transpose.
|
||||
* @return {!Map<V,K>} A transposed version of the given map.
|
||||
* @template K,V
|
||||
*/
|
||||
function transpose(map) {
|
||||
const /** !Map<V,K> */ transposed = new Map();
|
||||
for (const key of map.keys()) {
|
||||
const val = map.get(key);
|
||||
transposed.set(val, key);
|
||||
}
|
||||
return transposed;
|
||||
}
|
||||
exports.transpose = transpose;
|
||||
|
||||
/**
|
||||
* ToObject returns a new object whose properties are the keys from the Map.
|
||||
* @param {!MapLike<K,V>} map The map to convert into an object.
|
||||
* @return {!Object<K,V>} An object representation of the Map.
|
||||
* @template K,V
|
||||
*/
|
||||
function toObject(map) {
|
||||
const /** !Object<K,V> */ obj = {};
|
||||
for (const key of map.keys()) {
|
||||
obj[key] = map.get(key);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
exports.toObject = toObject;
|
||||
Executable
+747
@@ -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();
|
||||
};
|
||||
Executable
+158
@@ -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--;
|
||||
};
|
||||
Executable
+72
@@ -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;
|
||||
Executable
+43
@@ -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';
|
||||
Executable
+364
@@ -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: ';
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Implements the disposable interface.
|
||||
*/
|
||||
|
||||
goog.provide('goog.Disposable');
|
||||
|
||||
goog.require('goog.disposable.IDisposable');
|
||||
goog.require('goog.dispose');
|
||||
/**
|
||||
* TODO(user): Remove this require.
|
||||
* @suppress {extraRequire}
|
||||
*/
|
||||
goog.require('goog.disposeAll');
|
||||
|
||||
/**
|
||||
* Class that provides the basic implementation for disposable objects. If your
|
||||
* class holds references or resources that can't be collected by standard GC,
|
||||
* it should extend this class or implement the disposable interface (defined
|
||||
* in goog.disposable.IDisposable). See description of
|
||||
* goog.disposable.IDisposable for examples of cleanup.
|
||||
* @constructor
|
||||
* @implements {goog.disposable.IDisposable}
|
||||
*/
|
||||
goog.Disposable = function() {
|
||||
'use strict';
|
||||
/**
|
||||
* If monitoring the goog.Disposable instances is enabled, stores the creation
|
||||
* stack trace of the Disposable instance.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.creationStack;
|
||||
|
||||
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
|
||||
if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
|
||||
this.creationStack = new Error().stack;
|
||||
}
|
||||
goog.Disposable.instances_[goog.getUid(this)] = this;
|
||||
}
|
||||
// Support sealing
|
||||
this.disposed_ = this.disposed_;
|
||||
this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number} Different monitoring modes for Disposable.
|
||||
*/
|
||||
goog.Disposable.MonitoringMode = {
|
||||
/**
|
||||
* No monitoring.
|
||||
*/
|
||||
OFF: 0,
|
||||
/**
|
||||
* Creating and disposing the goog.Disposable instances is monitored. All
|
||||
* disposable objects need to call the `goog.Disposable` base
|
||||
* constructor. The PERMANENT mode must be switched on before creating any
|
||||
* goog.Disposable instances.
|
||||
*/
|
||||
PERMANENT: 1,
|
||||
/**
|
||||
* INTERACTIVE mode can be switched on and off on the fly without producing
|
||||
* errors. It also doesn't warn if the disposable objects don't call the
|
||||
* `goog.Disposable` base constructor.
|
||||
*/
|
||||
INTERACTIVE: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {number} The monitoring mode of the goog.Disposable
|
||||
* instances. Default is OFF. Switching on the monitoring is only
|
||||
* recommended for debugging because it has a significant impact on
|
||||
* performance and memory usage. If switched off, the monitoring code
|
||||
* compiles down to 0 bytes.
|
||||
*/
|
||||
goog.Disposable.MONITORING_MODE =
|
||||
goog.define('goog.Disposable.MONITORING_MODE', 0);
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to attach creation stack to each created disposable
|
||||
* instance; This is only relevant for when MonitoringMode != OFF.
|
||||
*/
|
||||
goog.Disposable.INCLUDE_STACK_ON_CREATION =
|
||||
goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
|
||||
|
||||
|
||||
/**
|
||||
* Maps the unique ID of every undisposed `goog.Disposable` object to
|
||||
* the object itself.
|
||||
* @type {!Object<number, !goog.Disposable>}
|
||||
* @private
|
||||
*/
|
||||
goog.Disposable.instances_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<!goog.Disposable>} All `goog.Disposable` objects that
|
||||
* haven't been disposed of.
|
||||
*/
|
||||
goog.Disposable.getUndisposedObjects = function() {
|
||||
'use strict';
|
||||
var ret = [];
|
||||
for (var id in goog.Disposable.instances_) {
|
||||
if (goog.Disposable.instances_.hasOwnProperty(id)) {
|
||||
ret.push(goog.Disposable.instances_[Number(id)]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the registry of undisposed objects but doesn't dispose of them.
|
||||
*/
|
||||
goog.Disposable.clearUndisposedObjects = function() {
|
||||
'use strict';
|
||||
goog.Disposable.instances_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the object has been disposed of.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.Disposable.prototype.disposed_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Callbacks to invoke when this object is disposed.
|
||||
* @type {Array<!Function>}
|
||||
* @private
|
||||
*/
|
||||
goog.Disposable.prototype.onDisposeCallbacks_;
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the object has been disposed of.
|
||||
* @override
|
||||
*/
|
||||
goog.Disposable.prototype.isDisposed = function() {
|
||||
'use strict';
|
||||
return this.disposed_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the object has been disposed of.
|
||||
* @deprecated Use {@link #isDisposed} instead.
|
||||
*/
|
||||
goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the object. If the object hasn't already been disposed of, calls
|
||||
* {@link #disposeInternal}. Classes that extend `goog.Disposable` should
|
||||
* override {@link #disposeInternal} in order to cleanup references, resources
|
||||
* and other disposable objects. Reentrant.
|
||||
*
|
||||
* @return {void} Nothing.
|
||||
* @override
|
||||
*/
|
||||
goog.Disposable.prototype.dispose = function() {
|
||||
'use strict';
|
||||
if (!this.disposed_) {
|
||||
// Set disposed_ to true first, in case during the chain of disposal this
|
||||
// gets disposed recursively.
|
||||
this.disposed_ = true;
|
||||
this.disposeInternal();
|
||||
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
|
||||
var uid = goog.getUid(this);
|
||||
if (goog.Disposable.MONITORING_MODE ==
|
||||
goog.Disposable.MonitoringMode.PERMANENT &&
|
||||
!goog.Disposable.instances_.hasOwnProperty(uid)) {
|
||||
throw new Error(
|
||||
this + ' did not call the goog.Disposable base ' +
|
||||
'constructor or was disposed of after a clearUndisposedObjects ' +
|
||||
'call');
|
||||
}
|
||||
if (goog.Disposable.MONITORING_MODE !=
|
||||
goog.Disposable.MonitoringMode.OFF &&
|
||||
this.onDisposeCallbacks_ && this.onDisposeCallbacks_.length > 0) {
|
||||
throw new Error(
|
||||
this + ' did not empty its onDisposeCallbacks queue. This ' +
|
||||
'probably means it overrode dispose() or disposeInternal() ' +
|
||||
'without calling the superclass\' method.');
|
||||
}
|
||||
delete goog.Disposable.instances_[uid];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Associates a disposable object with this object so that they will be disposed
|
||||
* together.
|
||||
* @param {goog.disposable.IDisposable} disposable that will be disposed when
|
||||
* this object is disposed.
|
||||
*/
|
||||
goog.Disposable.prototype.registerDisposable = function(disposable) {
|
||||
'use strict';
|
||||
this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes a callback function when this object is disposed. Callbacks are
|
||||
* invoked in the order in which they were added. If a callback is added to
|
||||
* an already disposed Disposable, it will be called immediately.
|
||||
* @param {function(this:T):?} callback The callback function.
|
||||
* @param {T=} opt_scope An optional scope to call the callback in.
|
||||
* @template T
|
||||
*/
|
||||
goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
|
||||
'use strict';
|
||||
if (this.disposed_) {
|
||||
opt_scope !== undefined ? callback.call(opt_scope) : callback();
|
||||
return;
|
||||
}
|
||||
if (!this.onDisposeCallbacks_) {
|
||||
this.onDisposeCallbacks_ = [];
|
||||
}
|
||||
|
||||
this.onDisposeCallbacks_.push(
|
||||
opt_scope !== undefined ? goog.bind(callback, opt_scope) : callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs appropriate cleanup. See description of goog.disposable.IDisposable
|
||||
* for examples. Classes that extend `goog.Disposable` should override this
|
||||
* method. Not reentrant. To avoid calling it twice, it must only be called from
|
||||
* the subclass' `disposeInternal` method. Everywhere else the public `dispose`
|
||||
* method must be used. For example:
|
||||
*
|
||||
* <pre>
|
||||
* mypackage.MyClass = function() {
|
||||
* mypackage.MyClass.base(this, 'constructor');
|
||||
* // Constructor logic specific to MyClass.
|
||||
* ...
|
||||
* };
|
||||
* goog.inherits(mypackage.MyClass, goog.Disposable);
|
||||
*
|
||||
* mypackage.MyClass.prototype.disposeInternal = function() {
|
||||
* // Dispose logic specific to MyClass.
|
||||
* ...
|
||||
* // Call superclass's disposeInternal at the end of the subclass's, like
|
||||
* // in C++, to avoid hard-to-catch issues.
|
||||
* mypackage.MyClass.base(this, 'disposeInternal');
|
||||
* };
|
||||
* </pre>
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
goog.Disposable.prototype.disposeInternal = function() {
|
||||
'use strict';
|
||||
if (this.onDisposeCallbacks_) {
|
||||
while (this.onDisposeCallbacks_.length) {
|
||||
this.onDisposeCallbacks_.shift()();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns True if we can verify the object is disposed.
|
||||
* Calls `isDisposed` on the argument if it supports it. If obj
|
||||
* is not an object with an isDisposed() method, return false.
|
||||
* @param {*} obj The object to investigate.
|
||||
* @return {boolean} True if we can verify the object is disposed.
|
||||
*/
|
||||
goog.Disposable.isDisposed = function(obj) {
|
||||
'use strict';
|
||||
if (obj && typeof obj.isDisposed == 'function') {
|
||||
return obj.isDisposed();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The dispose method is used to clean up references and
|
||||
* resources.
|
||||
*/
|
||||
|
||||
goog.module('goog.dispose');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
/**
|
||||
* Calls `dispose` on the argument if it supports it. If obj is not an
|
||||
* object with a dispose() method, this is a no-op.
|
||||
* @param {*} obj The object to dispose of.
|
||||
*/
|
||||
function dispose(obj) {
|
||||
if (obj && typeof obj.dispose == 'function') {
|
||||
obj.dispose();
|
||||
}
|
||||
}
|
||||
exports = dispose;
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The disposeAll method is used to clean up references and
|
||||
* resources.
|
||||
*/
|
||||
|
||||
goog.module('goog.disposeAll');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const dispose = goog.require('goog.dispose');
|
||||
|
||||
/**
|
||||
* Calls `dispose` on each member of the list that supports it. (If the
|
||||
* member is an ArrayLike, then `goog.disposeAll()` will be called
|
||||
* recursively on each of its members.) If the member is not an object with a
|
||||
* `dispose()` method, then it is ignored.
|
||||
* @param {...*} var_args The list.
|
||||
*/
|
||||
function disposeAll(var_args) {
|
||||
for (let i = 0, len = arguments.length; i < len; ++i) {
|
||||
const disposable = arguments[i];
|
||||
if (goog.isArrayLike(disposable)) {
|
||||
disposeAll.apply(null, disposable);
|
||||
} else {
|
||||
dispose(disposable);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports = disposeAll;
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the disposable interface. A disposable object
|
||||
* has a dispose method to to clean up references and resources.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.disposable.IDisposable');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for a disposable object. If a instance requires cleanup, it should
|
||||
* implement this interface (it may subclass goog.Disposable).
|
||||
*
|
||||
* Examples of cleanup that can be done in `dispose` method:
|
||||
* 1. Remove event listeners.
|
||||
* 2. Cancel timers (setTimeout, setInterval, goog.Timer).
|
||||
* 3. Call `dispose` on other disposable objects hold by current object.
|
||||
* 4. Close connections (e.g. WebSockets).
|
||||
*
|
||||
* Note that it's not required to delete properties (e.g. DOM nodes) or set them
|
||||
* to null as garbage collector will collect them assuming that references to
|
||||
* current object will be lost after it is disposed.
|
||||
*
|
||||
* See also http://go/mdn/JavaScript/Memory_Management.
|
||||
*
|
||||
* @record
|
||||
*/
|
||||
goog.disposable.IDisposable = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the object and its resources.
|
||||
* @return {void} Nothing.
|
||||
*/
|
||||
goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the object has been disposed of.
|
||||
*/
|
||||
goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
|
||||
Executable
+398
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.asserts');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
/**
|
||||
* @fileoverview Custom assertions to ensure that an element has the appropriate
|
||||
* type.
|
||||
*
|
||||
* Using a goog.dom.safe wrapper on an object on the incorrect type (via an
|
||||
* incorrect static type cast) can result in security bugs: For instance,
|
||||
* g.d.s.setAnchorHref ensures that the URL assigned to the .href attribute
|
||||
* satisfies the SafeUrl contract, i.e., is safe to dereference as a hyperlink.
|
||||
* However, the value assigned to a HTMLLinkElement's .href property requires
|
||||
* the stronger TrustedResourceUrl contract, since it can refer to a stylesheet.
|
||||
* Thus, using g.d.s.setAnchorHref on an (incorrectly statically typed) object
|
||||
* of type HTMLLinkElement can result in a security vulnerability.
|
||||
* Assertions of the correct run-time type help prevent such incorrect use.
|
||||
*
|
||||
* In some cases, code using the DOM API is tested using mock objects (e.g., a
|
||||
* plain object such as {'href': url} instead of an actual Location object).
|
||||
* To allow such mocking, the assertions permit objects of types that are not
|
||||
* relevant DOM API objects at all (for instance, not Element or Location).
|
||||
*
|
||||
* Note that instanceof checks don't work straightforwardly in older versions of
|
||||
* IE, or across frames (see,
|
||||
* http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object,
|
||||
* http://stackoverflow.com/questions/26248599/instanceof-htmlelement-in-iframe-is-not-element-or-object).
|
||||
*
|
||||
* Hence, these assertions may pass vacuously in such scenarios. The resulting
|
||||
* risk of security bugs is limited by the following factors:
|
||||
* - A bug can only arise in scenarios involving incorrect static typing (the
|
||||
* wrapper methods are statically typed to demand objects of the appropriate,
|
||||
* precise type).
|
||||
* - Typically, code is tested and exercised in multiple browsers.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a Location.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where DOM APIs might
|
||||
* be mocked, also accepts any other type except for subtypes of {!Element}.
|
||||
* This is to ensure that, for instance, HTMLLinkElement is not being used in
|
||||
* place of a Location, since this could result in security bugs due to stronger
|
||||
* contracts required for assignments to the href property of the latter.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!Location}
|
||||
*/
|
||||
goog.dom.asserts.assertIsLocation = function(o) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS) {
|
||||
var win = goog.dom.asserts.getWindow_(o);
|
||||
if (win) {
|
||||
if (!o || (!(o instanceof win.Location) && o instanceof win.Element)) {
|
||||
goog.asserts.fail(
|
||||
'Argument is not a Location (or a non-Element mock); got: %s',
|
||||
goog.dom.asserts.debugStringForType_(o));
|
||||
}
|
||||
}
|
||||
}
|
||||
return /** @type {!Location} */ (o);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that a given object is either the given subtype of Element
|
||||
* or a non-Element, non-Location Mock.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where DOM
|
||||
* APIs might be mocked, also accepts any other type except for
|
||||
* subtypes of {!Element}. This is to ensure that, for instance,
|
||||
* HTMLScriptElement is not being used in place of a HTMLImageElement,
|
||||
* since this could result in security bugs due to stronger contracts
|
||||
* required for assignments to the src property of the latter.
|
||||
*
|
||||
* The DOM type is looked up in the window the object belongs to. In
|
||||
* some contexts, this might not be possible (e.g. when running tests
|
||||
* outside a browser, cross-domain lookup). In this case, the
|
||||
* assertions are skipped.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @param {string} typename The name of the DOM type.
|
||||
* @return {!Element} The object.
|
||||
* @private
|
||||
*/
|
||||
// TODO(bangert): Make an analog of goog.dom.TagName to correctly handle casts?
|
||||
goog.dom.asserts.assertIsElementType_ = function(o, typename) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS) {
|
||||
var win = goog.dom.asserts.getWindow_(o);
|
||||
if (win && typeof win[typename] != 'undefined') {
|
||||
if (!o ||
|
||||
(!(o instanceof win[typename]) &&
|
||||
(o instanceof win.Location || o instanceof win.Element))) {
|
||||
goog.asserts.fail(
|
||||
'Argument is not a %s (or a non-Element, non-Location mock); ' +
|
||||
'got: %s',
|
||||
typename, goog.dom.asserts.debugStringForType_(o));
|
||||
}
|
||||
}
|
||||
}
|
||||
return /** @type {!Element} */ (o);
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLAnchorElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not of type Location nor a subtype
|
||||
* of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLAnchorElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlAnchorElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLAnchorElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLAnchorElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLAnchorElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLButtonElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLButtonElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlButtonElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLButtonElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLButtonElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLButtonElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLLinkElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLLinkElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlLinkElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLLinkElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLLinkElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLLinkElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLImageElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLImageElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlImageElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLImageElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLImageElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLImageElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLAudioElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLAudioElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlAudioElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLAudioElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLAudioElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLAudioElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLVideoElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLVideoElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlVideoElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLVideoElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLVideoElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLVideoElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLInputElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLInputElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlInputElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLInputElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLInputElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLInputElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLTextAreaElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLTextAreaElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlTextAreaElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLTextAreaElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLTextAreaElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLTextAreaElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLCanvasElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLCanvasElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlCanvasElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLCanvasElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLCanvasElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLCanvasElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLEmbedElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLEmbedElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlEmbedElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLEmbedElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLEmbedElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLEmbedElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLFormElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLFormElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlFormElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLFormElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLFormElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLFormElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLFrameElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLFrameElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlFrameElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLFrameElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLFrameElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLFrameElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLIFrameElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLIFrameElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlIFrameElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLIFrameElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLIFrameElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLIFrameElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLObjectElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLObjectElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlObjectElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLObjectElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLObjectElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLObjectElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that a given object is a HTMLScriptElement.
|
||||
*
|
||||
* To permit this assertion to pass in the context of tests where elements might
|
||||
* be mocked, also accepts objects that are not a subtype of Element.
|
||||
*
|
||||
* @param {?Object} o The object whose type to assert.
|
||||
* @return {!HTMLScriptElement}
|
||||
* @deprecated Use goog.asserts.dom.assertIsHtmlScriptElement instead.
|
||||
*/
|
||||
goog.dom.asserts.assertIsHTMLScriptElement = function(o) {
|
||||
'use strict';
|
||||
return /** @type {!HTMLScriptElement} */ (
|
||||
goog.dom.asserts.assertIsElementType_(o, 'HTMLScriptElement'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representation of a value's type.
|
||||
*
|
||||
* @param {*} value An object, or primitive.
|
||||
* @return {string} The best display name for the value.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.asserts.debugStringForType_ = function(value) {
|
||||
'use strict';
|
||||
if (goog.isObject(value)) {
|
||||
try {
|
||||
return /** @type {string|undefined} */ (value.constructor.displayName) ||
|
||||
value.constructor.name || Object.prototype.toString.call(value);
|
||||
} catch (e) {
|
||||
return '<object could not be stringified>';
|
||||
}
|
||||
} else {
|
||||
return value === undefined ? 'undefined' :
|
||||
value === null ? 'null' : typeof value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets window of element.
|
||||
* @param {?Object} o
|
||||
* @return {?Window}
|
||||
* @private
|
||||
* @suppress {strictMissingProperties} ownerDocument not defined on Object
|
||||
*/
|
||||
goog.dom.asserts.getWindow_ = function(o) {
|
||||
'use strict';
|
||||
try {
|
||||
var doc = o && o.ownerDocument;
|
||||
// This can throw “Blocked a frame with origin "chrome-extension://..." from
|
||||
// accessing a cross-origin frame” in Chrome extension.
|
||||
var win =
|
||||
doc && /** @type {?Window} */ (doc.defaultView || doc.parentWindow);
|
||||
win = win || /** @type {!Window} */ (goog.global);
|
||||
// This can throw “Permission denied to access property "Element" on
|
||||
// cross-origin object”.
|
||||
if (win.Element && win.Location) {
|
||||
return win;
|
||||
}
|
||||
} catch (ex) {
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Browser capability checks for the dom package.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.BrowserFeature');
|
||||
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether we know at compile time that the browser doesn't
|
||||
* support OffscreenCanvas.
|
||||
*/
|
||||
goog.dom.BrowserFeature.ASSUME_NO_OFFSCREEN_CANVAS =
|
||||
goog.define('goog.dom.ASSUME_NO_OFFSCREEN_CANVAS', false);
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether we know at compile time that the browser supports
|
||||
* all OffscreenCanvas contexts.
|
||||
*/
|
||||
// TODO(user): Eventually this should default to "FEATURESET_YEAR >= 202X".
|
||||
goog.dom.BrowserFeature.ASSUME_OFFSCREEN_CANVAS =
|
||||
goog.define('goog.dom.ASSUME_OFFSCREEN_CANVAS', false);
|
||||
|
||||
/**
|
||||
* Detects if a particular OffscreenCanvas context is supported.
|
||||
* @param {string} contextName name of the context to test.
|
||||
* @return {boolean} Whether the browser supports this OffscreenCanvas context.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.BrowserFeature.detectOffscreenCanvas_ = function(contextName) {
|
||||
'use strict';
|
||||
// This code only gets removed because we forced @nosideeffects on
|
||||
// the functions. See: b/138802376
|
||||
try {
|
||||
return Boolean(new self.OffscreenCanvas(0, 0).getContext(contextName));
|
||||
} catch (ex) {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the browser supports OffscreenCanvas 2D context.
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.OFFSCREEN_CANVAS_2D =
|
||||
!goog.dom.BrowserFeature.ASSUME_NO_OFFSCREEN_CANVAS &&
|
||||
(goog.dom.BrowserFeature.ASSUME_OFFSCREEN_CANVAS ||
|
||||
goog.dom.BrowserFeature.detectOffscreenCanvas_('2d'));
|
||||
|
||||
/**
|
||||
* Whether attributes 'name' and 'type' can be added to an element after it's
|
||||
* created. False in Internet Explorer prior to version 9.
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES = true;
|
||||
|
||||
/**
|
||||
* Whether we can use element.children to access an element's Element
|
||||
* children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
|
||||
* nodes in the collection.)
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE = true;
|
||||
|
||||
/**
|
||||
* Opera, Safari 3, and Internet Explorer 9 all support innerText but they
|
||||
* include text nodes in script and style tags. Not document-mode-dependent.
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.CAN_USE_INNER_TEXT = false;
|
||||
|
||||
/**
|
||||
* MSIE, Opera, and Safari>=4 support element.parentElement to access an
|
||||
* element's parent if it is an Element.
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY =
|
||||
goog.userAgent.IE || goog.userAgent.WEBKIT;
|
||||
|
||||
/**
|
||||
* Whether NoScope elements need a scoped element written before them in
|
||||
* innerHTML.
|
||||
* MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT = goog.userAgent.IE;
|
||||
Executable
+3499
File diff suppressed because it is too large
Load Diff
Executable
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.HtmlElement');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This subclass of HTMLElement is used when only a HTMLElement is possible and
|
||||
* not any of its subclasses. Normally, a type can refer to an instance of
|
||||
* itself or an instance of any subtype. More concretely, if HTMLElement is used
|
||||
* then the compiler must assume that it might still be e.g. HTMLScriptElement.
|
||||
* With this, the type check knows that it couldn't be any special element.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {HTMLElement}
|
||||
*/
|
||||
goog.dom.HtmlElement = function() {};
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines the goog.dom.InputType enum. This enumerates all
|
||||
* input element types (for INPUT, BUTTON, SELECT and TEXTAREA elements) in
|
||||
* either the W3C HTML 4.01 index of elements or the HTML5 draft specification.
|
||||
*
|
||||
* References:
|
||||
* http://www.w3.org/TR/html401/sgml/dtd.html#InputType
|
||||
* http://www.w3.org/TR/html-markup/input.html#input
|
||||
* https://html.spec.whatwg.org/multipage/forms.html#dom-input-type
|
||||
* https://html.spec.whatwg.org/multipage/forms.html#dom-button-type
|
||||
* https://html.spec.whatwg.org/multipage/forms.html#dom-select-type
|
||||
* https://html.spec.whatwg.org/multipage/forms.html#dom-textarea-type
|
||||
*/
|
||||
goog.provide('goog.dom.InputType');
|
||||
|
||||
|
||||
/**
|
||||
* Enum of all input types (for INPUT, BUTTON, SELECT and TEXTAREA elements)
|
||||
* specified by the W3C HTML4.01 and HTML5 specifications.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.InputType = {
|
||||
BUTTON: 'button',
|
||||
CHECKBOX: 'checkbox',
|
||||
COLOR: 'color',
|
||||
DATE: 'date',
|
||||
DATETIME: 'datetime',
|
||||
DATETIME_LOCAL: 'datetime-local',
|
||||
EMAIL: 'email',
|
||||
FILE: 'file',
|
||||
HIDDEN: 'hidden',
|
||||
IMAGE: 'image',
|
||||
MENU: 'menu',
|
||||
MONTH: 'month',
|
||||
NUMBER: 'number',
|
||||
PASSWORD: 'password',
|
||||
RADIO: 'radio',
|
||||
RANGE: 'range',
|
||||
RESET: 'reset',
|
||||
SEARCH: 'search',
|
||||
SELECT_MULTIPLE: 'select-multiple',
|
||||
SELECT_ONE: 'select-one',
|
||||
SUBMIT: 'submit',
|
||||
TEL: 'tel',
|
||||
TEXT: 'text',
|
||||
TEXTAREA: 'textarea',
|
||||
TIME: 'time',
|
||||
URL: 'url',
|
||||
WEEK: 'week'
|
||||
};
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of goog.dom.NodeType.
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.NodeType');
|
||||
|
||||
|
||||
/**
|
||||
* Constants for the nodeType attribute in the Node interface.
|
||||
*
|
||||
* These constants match those specified in the Node interface. These are
|
||||
* usually present on the Node object in recent browsers, but not in older
|
||||
* browsers (specifically, early IEs) and thus are given here.
|
||||
*
|
||||
* In some browsers (early IEs), these are not defined on the Node object,
|
||||
* so they are provided here.
|
||||
*
|
||||
* See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.dom.NodeType = {
|
||||
ELEMENT: 1,
|
||||
ATTRIBUTE: 2,
|
||||
TEXT: 3,
|
||||
CDATA_SECTION: 4,
|
||||
ENTITY_REFERENCE: 5,
|
||||
ENTITY: 6,
|
||||
PROCESSING_INSTRUCTION: 7,
|
||||
COMMENT: 8,
|
||||
DOCUMENT: 9,
|
||||
DOCUMENT_TYPE: 10,
|
||||
DOCUMENT_FRAGMENT: 11,
|
||||
NOTATION: 12
|
||||
};
|
||||
Executable
+941
@@ -0,0 +1,941 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Type-safe wrappers for unsafe DOM APIs.
|
||||
*
|
||||
* This file provides type-safe wrappers for DOM APIs that can result in
|
||||
* cross-site scripting (XSS) vulnerabilities, if the API is supplied with
|
||||
* untrusted (attacker-controlled) input. Instead of plain strings, the type
|
||||
* safe wrappers consume values of types from the goog.html package whose
|
||||
* contract promises that values are safe to use in the corresponding context.
|
||||
*
|
||||
* Hence, a program that exclusively uses the wrappers in this file (i.e., whose
|
||||
* only reference to security-sensitive raw DOM APIs are in this file) is
|
||||
* guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo
|
||||
* correctness of code that produces values of the respective goog.html types,
|
||||
* and absent code that violates type safety).
|
||||
*
|
||||
* For example, assigning to an element's .innerHTML property a string that is
|
||||
* derived (even partially) from untrusted input typically results in an XSS
|
||||
* vulnerability. The type-safe wrapper goog.dom.safe.setInnerHtml consumes a
|
||||
* value of type goog.html.SafeHtml, whose contract states that using its values
|
||||
* in a HTML context will not result in XSS. Hence a program that is free of
|
||||
* direct assignments to any element's innerHTML property (with the exception of
|
||||
* the assignment to .innerHTML in this file) is guaranteed to be free of XSS
|
||||
* due to assignment of untrusted strings to the innerHTML property.
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.safe');
|
||||
goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom.asserts');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.html.SafeScript');
|
||||
goog.require('goog.html.SafeStyle');
|
||||
goog.require('goog.html.SafeUrl');
|
||||
goog.require('goog.html.TrustedResourceUrl');
|
||||
goog.require('goog.html.uncheckedconversions');
|
||||
goog.require('goog.string.Const');
|
||||
goog.require('goog.string.internal');
|
||||
|
||||
|
||||
/** @enum {string} */
|
||||
goog.dom.safe.InsertAdjacentHtmlPosition = {
|
||||
AFTERBEGIN: 'afterbegin',
|
||||
AFTEREND: 'afterend',
|
||||
BEFOREBEGIN: 'beforebegin',
|
||||
BEFOREEND: 'beforeend'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts known-safe HTML into a Node, at the specified position.
|
||||
* @param {!Node} node The node on which to call insertAdjacentHTML.
|
||||
* @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where
|
||||
* to insert the HTML.
|
||||
* @param {!goog.html.SafeHtml} html The known-safe HTML to insert.
|
||||
*/
|
||||
goog.dom.safe.insertAdjacentHtml = function(node, position, html) {
|
||||
'use strict';
|
||||
node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrapTrustedHTML(html));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tags not allowed in goog.dom.safe.setInnerHtml.
|
||||
* @private @const {!Object<string, boolean>}
|
||||
*/
|
||||
goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_ = {
|
||||
'MATH': true,
|
||||
'SCRIPT': true,
|
||||
'STYLE': true,
|
||||
'SVG': true,
|
||||
'TEMPLATE': true
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether assigning to innerHTML results in a non-spec-compliant clean-up. Used
|
||||
* to define goog.dom.safe.unsafeSetInnerHtmlDoNotUseOrElse.
|
||||
*
|
||||
* <p>As mentioned in https://stackoverflow.com/questions/28741528, re-rendering
|
||||
* an element in IE by setting innerHTML causes IE to recursively disconnect all
|
||||
* parent/children connections that were in the previous contents of the
|
||||
* element. Unfortunately, this can unexpectedly result in confusing cases where
|
||||
* a function is run (typically asynchronously) on element that has since
|
||||
* disconnected from the DOM but assumes the presence of its children. A simple
|
||||
* workaround is to remove all children first. Testing on IE11 via
|
||||
* https://jsperf.com/innerhtml-vs-removechild/239, removeChild seems to be
|
||||
* ~10x faster than innerHTML='' for a large number of children (perhaps due
|
||||
* to the latter's recursive behavior), implying that this workaround would
|
||||
* not hurt performance and might actually improve it.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.safe.isInnerHtmlCleanupRecursive_ =
|
||||
goog.functions.cacheReturnValue(function() {
|
||||
'use strict';
|
||||
// `document` missing in some test frameworks.
|
||||
if (goog.DEBUG && typeof document === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
// Create 3 nested <div>s without using innerHTML.
|
||||
// We're not chaining the appendChilds in one call, as this breaks
|
||||
// in a DocumentFragment.
|
||||
var div = document.createElement('div');
|
||||
var childDiv = document.createElement('div');
|
||||
childDiv.appendChild(document.createElement('div'));
|
||||
div.appendChild(childDiv);
|
||||
// `firstChild` is null in Google Js Test.
|
||||
if (goog.DEBUG && !div.firstChild) {
|
||||
return false;
|
||||
}
|
||||
var innerChild = div.firstChild.firstChild;
|
||||
div.innerHTML =
|
||||
goog.html.SafeHtml.unwrapTrustedHTML(goog.html.SafeHtml.EMPTY);
|
||||
return !innerChild.parentElement;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Assigns HTML to an element's innerHTML property. Helper to use only here and
|
||||
* in soy.js.
|
||||
* @param {?Element|?ShadowRoot} elem The element whose innerHTML is to be
|
||||
* assigned to.
|
||||
* @param {!goog.html.SafeHtml} html
|
||||
*/
|
||||
goog.dom.safe.unsafeSetInnerHtmlDoNotUseOrElse = function(elem, html) {
|
||||
'use strict';
|
||||
// See comment above goog.dom.safe.isInnerHtmlCleanupRecursive_.
|
||||
if (goog.dom.safe.isInnerHtmlCleanupRecursive_()) {
|
||||
while (elem.lastChild) {
|
||||
elem.removeChild(elem.lastChild);
|
||||
}
|
||||
}
|
||||
elem.innerHTML = goog.html.SafeHtml.unwrapTrustedHTML(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assigns known-safe HTML to an element's innerHTML property.
|
||||
* @param {!Element|!ShadowRoot} elem The element whose innerHTML is to be
|
||||
* assigned to.
|
||||
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
|
||||
* @throws {Error} If called with one of these tags: math, script, style, svg,
|
||||
* template.
|
||||
*/
|
||||
goog.dom.safe.setInnerHtml = function(elem, html) {
|
||||
'use strict';
|
||||
if (goog.asserts.ENABLE_ASSERTS && elem.tagName) {
|
||||
var tagName = elem.tagName.toUpperCase();
|
||||
if (goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_[tagName]) {
|
||||
throw new Error(
|
||||
'goog.dom.safe.setInnerHtml cannot be used to set content of ' +
|
||||
elem.tagName + '.');
|
||||
}
|
||||
}
|
||||
|
||||
goog.dom.safe.unsafeSetInnerHtmlDoNotUseOrElse(elem, html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assigns constant HTML to an element's innerHTML property.
|
||||
* @param {!Element} element The element whose innerHTML is to be assigned to.
|
||||
* @param {!goog.string.Const} constHtml The known-safe HTML to assign.
|
||||
* @throws {!Error} If called with one of these tags: math, script, style, svg,
|
||||
* template.
|
||||
*/
|
||||
goog.dom.safe.setInnerHtmlFromConstant = function(element, constHtml) {
|
||||
'use strict';
|
||||
goog.dom.safe.setInnerHtml(
|
||||
element,
|
||||
goog.html.uncheckedconversions
|
||||
.safeHtmlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from('Constant HTML to be immediatelly used.'),
|
||||
goog.string.Const.unwrap(constHtml)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assigns known-safe HTML to an element's outerHTML property.
|
||||
* @param {!Element} elem The element whose outerHTML is to be assigned to.
|
||||
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
|
||||
*/
|
||||
goog.dom.safe.setOuterHtml = function(elem, html) {
|
||||
'use strict';
|
||||
elem.outerHTML = goog.html.SafeHtml.unwrapTrustedHTML(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL a form element's action property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* form's action property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setFormElementAction(formEl, url);
|
||||
* which is a safe alternative to
|
||||
* formEl.action = url;
|
||||
* The latter can result in XSS vulnerabilities if url is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!Element} form The form element whose action property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setFormElementAction = function(form, url) {
|
||||
'use strict';
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
goog.dom.asserts.assertIsHTMLFormElement(form).action =
|
||||
goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a button element's formaction property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* button's formaction property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setButtonFormAction(buttonEl, url);
|
||||
* which is a safe alternative to
|
||||
* buttonEl.action = url;
|
||||
* The latter can result in XSS vulnerabilities if url is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!Element} button The button element whose action property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setButtonFormAction = function(button, url) {
|
||||
'use strict';
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
goog.dom.asserts.assertIsHTMLButtonElement(button).formAction =
|
||||
goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
/**
|
||||
* Safely assigns a URL to an input element's formaction property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* input's formaction property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setInputFormAction(inputEl, url);
|
||||
* which is a safe alternative to
|
||||
* inputEl.action = url;
|
||||
* The latter can result in XSS vulnerabilities if url is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!Element} input The input element whose action property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setInputFormAction = function(input, url) {
|
||||
'use strict';
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
goog.dom.asserts.assertIsHTMLInputElement(input).formAction =
|
||||
goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the given element's style property to the contents of the provided
|
||||
* SafeStyle object.
|
||||
* @param {!Element} elem
|
||||
* @param {!goog.html.SafeStyle} style
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setStyle = function(elem, style) {
|
||||
'use strict';
|
||||
elem.style.cssText = goog.html.SafeStyle.unwrap(style);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes known-safe HTML to a document.
|
||||
* @param {!Document} doc The document to be written to.
|
||||
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.documentWrite = function(doc, html) {
|
||||
'use strict';
|
||||
doc.write(goog.html.SafeHtml.unwrapTrustedHTML(html));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to an anchor element's href property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* anchor's href property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setAnchorHref(anchorEl, url);
|
||||
* which is a safe alternative to
|
||||
* anchorEl.href = url;
|
||||
* The latter can result in XSS vulnerabilities if url is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!HTMLAnchorElement} anchor The anchor element whose href property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setAnchorHref = function(anchor, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLAnchorElement(anchor);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
anchor.href = goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to an image element's src property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* image's src property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* @param {!HTMLImageElement} imageElement The image element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setImageSrc = function(imageElement, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLImageElement(imageElement);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
var allowDataUrl = /^data:image\//i.test(url);
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url, allowDataUrl);
|
||||
}
|
||||
imageElement.src = goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a audio element's src property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* audio's src property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* @param {!HTMLAudioElement} audioElement The audio element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setAudioSrc = function(audioElement, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLAudioElement(audioElement);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
var allowDataUrl = /^data:audio\//i.test(url);
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url, allowDataUrl);
|
||||
}
|
||||
audioElement.src = goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a video element's src property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* video's src property. If url is of type string however, it is first
|
||||
* sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* @param {!HTMLVideoElement} videoElement The video element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setVideoSrc = function(videoElement, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLVideoElement(videoElement);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
var allowDataUrl = /^data:video\//i.test(url);
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url, allowDataUrl);
|
||||
}
|
||||
videoElement.src = goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to an embed element's src property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setEmbedSrc(embedEl, url);
|
||||
* which is a safe alternative to
|
||||
* embedEl.src = url;
|
||||
* The latter can result in loading untrusted code unless it is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLEmbedElement} embed The embed element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
|
||||
*/
|
||||
goog.dom.safe.setEmbedSrc = function(embed, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLEmbedElement(embed);
|
||||
embed.src = goog.html.TrustedResourceUrl.unwrapTrustedScriptURL(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a frame element's src property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setFrameSrc(frameEl, url);
|
||||
* which is a safe alternative to
|
||||
* frameEl.src = url;
|
||||
* The latter can result in loading untrusted code unless it is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLFrameElement} frame The frame element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setFrameSrc = function(frame, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLFrameElement(frame);
|
||||
frame.src = goog.html.TrustedResourceUrl.unwrap(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to an iframe element's src property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setIframeSrc(iframeEl, url);
|
||||
* which is a safe alternative to
|
||||
* iframeEl.src = url;
|
||||
* The latter can result in loading untrusted code unless it is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLIFrameElement} iframe The iframe element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setIframeSrc = function(iframe, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLIFrameElement(iframe);
|
||||
iframe.src = goog.html.TrustedResourceUrl.unwrap(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns HTML to an iframe element's srcdoc property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setIframeSrcdoc(iframeEl, safeHtml);
|
||||
* which is a safe alternative to
|
||||
* iframeEl.srcdoc = html;
|
||||
* The latter can result in loading untrusted code.
|
||||
*
|
||||
* @param {!HTMLIFrameElement} iframe The iframe element whose srcdoc property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.SafeHtml} html The HTML to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setIframeSrcdoc = function(iframe, html) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLIFrameElement(iframe);
|
||||
iframe.srcdoc = goog.html.SafeHtml.unwrapTrustedHTML(html);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely sets a link element's href and rel properties. Whether or not
|
||||
* the URL assigned to href has to be a goog.html.TrustedResourceUrl
|
||||
* depends on the value of the rel property. If rel contains "stylesheet"
|
||||
* then a TrustedResourceUrl is required.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet');
|
||||
* which is a safe alternative to
|
||||
* linkEl.rel = 'stylesheet';
|
||||
* linkEl.href = url;
|
||||
* The latter can result in loading untrusted code unless it is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLLinkElement} link The link element whose href property
|
||||
* is to be assigned to.
|
||||
* @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL
|
||||
* to assign to the href property. Must be a TrustedResourceUrl if the
|
||||
* value assigned to rel contains "stylesheet". A string value is
|
||||
* sanitized with goog.html.SafeUrl.sanitize.
|
||||
* @param {string} rel The value to assign to the rel property.
|
||||
* @return {void}
|
||||
* @throws {Error} if rel contains "stylesheet" and url is not a
|
||||
* TrustedResourceUrl
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLLinkElement(link);
|
||||
link.rel = rel;
|
||||
if (goog.string.internal.caseInsensitiveContains(rel, 'stylesheet')) {
|
||||
goog.asserts.assert(
|
||||
url instanceof goog.html.TrustedResourceUrl,
|
||||
'URL must be TrustedResourceUrl because "rel" contains "stylesheet"');
|
||||
link.href = goog.html.TrustedResourceUrl.unwrap(url);
|
||||
const win = link.ownerDocument && link.ownerDocument.defaultView;
|
||||
const nonce = goog.dom.safe.getStyleNonce(win);
|
||||
if (nonce) {
|
||||
link.setAttribute('nonce', nonce);
|
||||
}
|
||||
} else if (url instanceof goog.html.TrustedResourceUrl) {
|
||||
link.href = goog.html.TrustedResourceUrl.unwrap(url);
|
||||
} else if (url instanceof goog.html.SafeUrl) {
|
||||
link.href = goog.html.SafeUrl.unwrap(url);
|
||||
} else { // string
|
||||
// SafeUrl.sanitize must return legitimate SafeUrl when passed a string.
|
||||
link.href = goog.html.SafeUrl.unwrap(
|
||||
goog.html.SafeUrl.sanitizeAssertUnchanged(url));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to an object element's data property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setObjectData(objectEl, url);
|
||||
* which is a safe alternative to
|
||||
* objectEl.data = url;
|
||||
* The latter can result in loading untrusted code unless setit is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLObjectElement} object The object element whose data property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setObjectData = function(object, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLObjectElement(object);
|
||||
object.data = goog.html.TrustedResourceUrl.unwrapTrustedScriptURL(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a script element's src property.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setScriptSrc(scriptEl, url);
|
||||
* which is a safe alternative to
|
||||
* scriptEl.src = url;
|
||||
* The latter can result in loading untrusted code unless it is ensured that
|
||||
* the URL refers to a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLScriptElement} script The script element whose src property
|
||||
* is to be assigned to.
|
||||
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setScriptSrc = function(script, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLScriptElement(script);
|
||||
script.src = goog.html.TrustedResourceUrl.unwrapTrustedScriptURL(url);
|
||||
goog.dom.safe.setNonceForScriptElement_(script);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a value to a script element's content.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setScriptContent(scriptEl, content);
|
||||
* which is a safe alternative to
|
||||
* scriptEl.text = content;
|
||||
* The latter can result in executing untrusted code unless it is ensured that
|
||||
* the code is loaded from a trustworthy resource.
|
||||
*
|
||||
* @param {!HTMLScriptElement} script The script element whose content is being
|
||||
* set.
|
||||
* @param {!goog.html.SafeScript} content The content to assign.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.dom.safe.setScriptContent = function(script, content) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsHTMLScriptElement(script);
|
||||
script.textContent = goog.html.SafeScript.unwrapTrustedScript(content);
|
||||
goog.dom.safe.setNonceForScriptElement_(script);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set nonce-based CSPs to dynamically created scripts.
|
||||
* @param {!HTMLScriptElement} script The script element whose nonce value
|
||||
* is to be calculated
|
||||
* @private
|
||||
*/
|
||||
goog.dom.safe.setNonceForScriptElement_ = function(script) {
|
||||
'use strict';
|
||||
var win = script.ownerDocument && script.ownerDocument.defaultView;
|
||||
const nonce = goog.dom.safe.getScriptNonce(win);
|
||||
if (nonce) {
|
||||
script.setAttribute('nonce', nonce);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely assigns a URL to a Location object's href property.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
|
||||
* loc's href property. If url is of type string however, it is first sanitized
|
||||
* using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.setLocationHref(document.location, redirectUrl);
|
||||
* which is a safe alternative to
|
||||
* document.location.href = redirectUrl;
|
||||
* The latter can result in XSS vulnerabilities if redirectUrl is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!Location} loc The Location object whose href property is to be
|
||||
* assigned to.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
|
||||
*/
|
||||
goog.dom.safe.setLocationHref = function(loc, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsLocation(loc);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
loc.href = goog.html.SafeUrl.unwrap(safeUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Safely assigns the URL of a Location object.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and
|
||||
* passed to Location#assign. If url is of type string however, it is
|
||||
* first sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.assignLocation(document.location, newUrl);
|
||||
* which is a safe alternative to
|
||||
* document.location.assign(newUrl);
|
||||
* The latter can result in XSS vulnerabilities if newUrl is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* This has the same behaviour as setLocationHref, however some test
|
||||
* mock Location.assign instead of a property assignment.
|
||||
*
|
||||
* @param {!Location} loc The Location object which is to be assigned.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.assignLocation = function(loc, url) {
|
||||
'use strict';
|
||||
goog.dom.asserts.assertIsLocation(loc);
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
loc.assign(goog.html.SafeUrl.unwrap(safeUrl));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely replaces the URL of a Location object.
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and
|
||||
* passed to Location#replace. If url is of type string however, it is
|
||||
* first sanitized using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.replaceLocation(document.location, newUrl);
|
||||
* which is a safe alternative to
|
||||
* document.location.replace(newUrl);
|
||||
* The latter can result in XSS vulnerabilities if newUrl is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {!Location} loc The Location object which is to be replaced.
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to assign.
|
||||
* @return {void}
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
*/
|
||||
goog.dom.safe.replaceLocation = function(loc, url) {
|
||||
'use strict';
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
loc.replace(goog.html.SafeUrl.unwrap(safeUrl));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely opens a URL in a new window (via window.open).
|
||||
*
|
||||
* If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to
|
||||
* window.open. If url is of type string however, it is first sanitized
|
||||
* using goog.html.SafeUrl.sanitize.
|
||||
*
|
||||
* Note that this function does not prevent leakages via the referer that is
|
||||
* sent by window.open. It is advised to only use this to open 1st party URLs.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.openInWindow(url);
|
||||
* which is a safe alternative to
|
||||
* window.open(url);
|
||||
* The latter can result in XSS vulnerabilities if url is a
|
||||
* user-/attacker-controlled value.
|
||||
*
|
||||
* @param {string|!goog.html.SafeUrl} url The URL to open.
|
||||
* @param {Window=} opt_openerWin Window of which to call the .open() method.
|
||||
* Defaults to the global window.
|
||||
* @param {!goog.string.Const|string=} opt_name Name of the window to open in.
|
||||
* Can be _top, etc as allowed by window.open(). This accepts string for
|
||||
* legacy reasons. Pass goog.string.Const if possible.
|
||||
* @param {string=} opt_specs Comma-separated list of specifications, same as
|
||||
* in window.open().
|
||||
* @return {Window} Window the url was opened in.
|
||||
*/
|
||||
goog.dom.safe.openInWindow = function(url, opt_openerWin, opt_name, opt_specs) {
|
||||
'use strict';
|
||||
/** @type {!goog.html.SafeUrl} */
|
||||
var safeUrl;
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
safeUrl = url;
|
||||
} else {
|
||||
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
|
||||
}
|
||||
var win = opt_openerWin || goog.global;
|
||||
// If opt_name is undefined, simply passing that in to open() causes IE to
|
||||
// reuse the current window instead of opening a new one. Thus we pass '' in
|
||||
// instead, which according to spec opens a new window. See
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#dom-open .
|
||||
var name = opt_name instanceof goog.string.Const ?
|
||||
goog.string.Const.unwrap(opt_name) :
|
||||
opt_name || '';
|
||||
// Do not pass opt_specs to window.open unless it was provided by the caller.
|
||||
// IE11 will use it as a signal to open a new window rather than a new tab
|
||||
// (even if it is undefined).
|
||||
if (opt_specs !== undefined) {
|
||||
return win.open(goog.html.SafeUrl.unwrap(safeUrl), name, opt_specs);
|
||||
} else {
|
||||
return win.open(goog.html.SafeUrl.unwrap(safeUrl), name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses the HTML as 'text/html'.
|
||||
* @param {!DOMParser} parser
|
||||
* @param {!goog.html.SafeHtml} html The HTML to be parsed.
|
||||
* @return {!Document}
|
||||
*/
|
||||
goog.dom.safe.parseFromStringHtml = function(parser, html) {
|
||||
'use strict';
|
||||
return goog.dom.safe.parseFromString(parser, html, 'text/html');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses the string.
|
||||
* @param {!DOMParser} parser
|
||||
* @param {!goog.html.SafeHtml} content Note: We don't have a special type for
|
||||
* XML or SVG supported by this function so we use SafeHtml.
|
||||
* @param {string} type
|
||||
* @return {!Document}
|
||||
*/
|
||||
goog.dom.safe.parseFromString = function(parser, content, type) {
|
||||
'use strict';
|
||||
return parser.parseFromString(
|
||||
goog.html.SafeHtml.unwrapTrustedHTML(content), type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely creates an HTMLImageElement from a Blob.
|
||||
*
|
||||
* Example usage:
|
||||
* goog.dom.safe.createImageFromBlob(blob);
|
||||
* which is a safe alternative to
|
||||
* image.src = createObjectUrl(blob)
|
||||
* The latter can result in executing malicious same-origin scripts from a bad
|
||||
* Blob.
|
||||
* @param {!Blob} blob The blob to create the image from.
|
||||
* @return {!HTMLImageElement} The image element created from the blob.
|
||||
* @throws {!Error} If called with a Blob with a MIME type other than image/.*.
|
||||
*/
|
||||
goog.dom.safe.createImageFromBlob = function(blob) {
|
||||
'use strict';
|
||||
// Any image/* MIME type is accepted as safe.
|
||||
if (!/^image\/.*/g.test(blob.type)) {
|
||||
throw new Error(
|
||||
'goog.dom.safe.createImageFromBlob only accepts MIME type image/.*.');
|
||||
}
|
||||
var objectUrl = goog.global.URL.createObjectURL(blob);
|
||||
var image = new goog.global.Image();
|
||||
image.onload = function() {
|
||||
'use strict';
|
||||
goog.global.URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
goog.dom.safe.setImageSrc(
|
||||
image,
|
||||
goog.html.uncheckedconversions
|
||||
.safeUrlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from('Image blob URL.'), objectUrl));
|
||||
return image;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DocumentFragment by parsing html in the context of a Range.
|
||||
* @param {!Range} range The Range object starting from the context node to
|
||||
* create a fragment in.
|
||||
* @param {!goog.html.SafeHtml} html HTML to create a fragment from.
|
||||
* @return {?DocumentFragment}
|
||||
*/
|
||||
goog.dom.safe.createContextualFragment = function(range, html) {
|
||||
'use strict';
|
||||
return range.createContextualFragment(
|
||||
goog.html.SafeHtml.unwrapTrustedHTML(html));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns CSP script nonce, if set for any <script> tag.
|
||||
* @param {?Window=} opt_window The window context used to retrieve the nonce.
|
||||
* Defaults to global context.
|
||||
* @return {string} CSP nonce or empty string if no nonce is present.
|
||||
*/
|
||||
goog.dom.safe.getScriptNonce = function(opt_window) {
|
||||
return goog.dom.safe.getNonce_('script[nonce]', opt_window);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns CSP style nonce, if set for any <style> or <link rel="stylesheet">
|
||||
* tag.
|
||||
* @param {?Window=} opt_window The window context used to retrieve the nonce.
|
||||
* Defaults to global context.
|
||||
* @return {string} CSP nonce or empty string if no nonce is present.
|
||||
*/
|
||||
goog.dom.safe.getStyleNonce = function(opt_window) {
|
||||
return goog.dom.safe.getNonce_(
|
||||
'style[nonce],link[rel="stylesheet"][nonce]', opt_window);
|
||||
};
|
||||
|
||||
/**
|
||||
* According to the CSP3 spec a nonce must be a valid base64 string.
|
||||
* @see https://www.w3.org/TR/CSP3/#grammardef-base64-value
|
||||
* @private @const
|
||||
*/
|
||||
goog.dom.safe.NONCE_PATTERN_ = /^[\w+/_-]+[=]{0,2}$/;
|
||||
|
||||
/**
|
||||
* Returns CSP nonce, if set for any tag of given type.
|
||||
* @param {string} selector Selector for locating the element with nonce.
|
||||
* @param {?Window=} win The window context used to retrieve the nonce.
|
||||
* @return {string} CSP nonce or empty string if no nonce is present.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.safe.getNonce_ = function(selector, win) {
|
||||
const doc = (win || goog.global).document;
|
||||
if (!doc.querySelector) {
|
||||
return '';
|
||||
}
|
||||
let el = doc.querySelector(selector);
|
||||
if (el) {
|
||||
// Try to get the nonce from the IDL property first, because browsers that
|
||||
// implement additional nonce protection features (currently only Chrome) to
|
||||
// prevent nonce stealing via CSS do not expose the nonce via attributes.
|
||||
// See https://github.com/whatwg/html/issues/2369
|
||||
const nonce = el['nonce'] || el.getAttribute('nonce');
|
||||
if (nonce && goog.dom.safe.NONCE_PATTERN_.test(nonce)) {
|
||||
return nonce;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
Executable
+457
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines the goog.dom.TagName class. Its constants enumerate
|
||||
* all HTML tag names specified in either the W3C HTML 4.01 index of elements
|
||||
* or the HTML5.1 specification.
|
||||
*
|
||||
* References:
|
||||
* https://www.w3.org/TR/html401/index/elements.html
|
||||
* https://www.w3.org/TR/html51/dom.html#elements
|
||||
*/
|
||||
goog.provide('goog.dom.TagName');
|
||||
|
||||
goog.require('goog.dom.HtmlElement');
|
||||
|
||||
/**
|
||||
* A tag name for an HTML element.
|
||||
*
|
||||
* This type is a lie. All instances are actually strings. Do not implement it.
|
||||
*
|
||||
* It exists because we need an object type to host the template type parameter,
|
||||
* and that's not possible with literal or enum types. It is a record type so
|
||||
* that runtime type checks don't try to validate the lie.
|
||||
*
|
||||
* @template T
|
||||
* @record
|
||||
*/
|
||||
goog.dom.TagName = class {
|
||||
/**
|
||||
* Cast a string into the tagname for the associated constructor.
|
||||
*
|
||||
* @template T
|
||||
* @param {string} name
|
||||
* @param {function(new:T, ...?)} type
|
||||
* @return {!goog.dom.TagName<T>}
|
||||
*/
|
||||
static cast(name, type) {
|
||||
return /** @type {?} */ (name);
|
||||
}
|
||||
|
||||
/** @suppress {unusedPrivateMembers} */
|
||||
constructor() {
|
||||
/** @private {null} */
|
||||
this.googDomTagName_doNotImplementThisTypeOrElse_;
|
||||
|
||||
/** @private {T} */
|
||||
this.ensureTypeScriptRemembersTypeT_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appease the compiler that instances are stringafiable for the
|
||||
* purpose of being a dictionary key.
|
||||
*
|
||||
* Never implemented; always backed by `String::toString`.
|
||||
*
|
||||
* @override
|
||||
* @return {string}
|
||||
*/
|
||||
toString() {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLAnchorElement>} */
|
||||
goog.dom.TagName.A = /** @type {?} */ ('A');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.ABBR = /** @type {?} */ ('ABBR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.ACRONYM = /** @type {?} */ ('ACRONYM');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.ADDRESS = /** @type {?} */ ('ADDRESS');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLAppletElement>} */
|
||||
goog.dom.TagName.APPLET = /** @type {?} */ ('APPLET');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLAreaElement>} */
|
||||
goog.dom.TagName.AREA = /** @type {?} */ ('AREA');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.ARTICLE = /** @type {?} */ ('ARTICLE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.ASIDE = /** @type {?} */ ('ASIDE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLAudioElement>} */
|
||||
goog.dom.TagName.AUDIO = /** @type {?} */ ('AUDIO');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.B = /** @type {?} */ ('B');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLBaseElement>} */
|
||||
goog.dom.TagName.BASE = /** @type {?} */ ('BASE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLBaseFontElement>} */
|
||||
goog.dom.TagName.BASEFONT = /** @type {?} */ ('BASEFONT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.BDI = /** @type {?} */ ('BDI');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.BDO = /** @type {?} */ ('BDO');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.BIG = /** @type {?} */ ('BIG');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLQuoteElement>} */
|
||||
goog.dom.TagName.BLOCKQUOTE = /** @type {?} */ ('BLOCKQUOTE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLBodyElement>} */
|
||||
goog.dom.TagName.BODY = /** @type {?} */ ('BODY');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLBRElement>} */
|
||||
goog.dom.TagName.BR = /** @type {?} */ ('BR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLButtonElement>} */
|
||||
goog.dom.TagName.BUTTON = /** @type {?} */ ('BUTTON');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLCanvasElement>} */
|
||||
goog.dom.TagName.CANVAS = /** @type {?} */ ('CANVAS');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableCaptionElement>} */
|
||||
goog.dom.TagName.CAPTION = /** @type {?} */ ('CAPTION');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.CENTER = /** @type {?} */ ('CENTER');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.CITE = /** @type {?} */ ('CITE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.CODE = /** @type {?} */ ('CODE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableColElement>} */
|
||||
goog.dom.TagName.COL = /** @type {?} */ ('COL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableColElement>} */
|
||||
goog.dom.TagName.COLGROUP = /** @type {?} */ ('COLGROUP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.COMMAND = /** @type {?} */ ('COMMAND');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.DATA = /** @type {?} */ ('DATA');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDataListElement>} */
|
||||
goog.dom.TagName.DATALIST = /** @type {?} */ ('DATALIST');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.DD = /** @type {?} */ ('DD');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLModElement>} */
|
||||
goog.dom.TagName.DEL = /** @type {?} */ ('DEL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDetailsElement>} */
|
||||
goog.dom.TagName.DETAILS = /** @type {?} */ ('DETAILS');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.DFN = /** @type {?} */ ('DFN');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDialogElement>} */
|
||||
goog.dom.TagName.DIALOG = /** @type {?} */ ('DIALOG');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDirectoryElement>} */
|
||||
goog.dom.TagName.DIR = /** @type {?} */ ('DIR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDivElement>} */
|
||||
goog.dom.TagName.DIV = /** @type {?} */ ('DIV');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLDListElement>} */
|
||||
goog.dom.TagName.DL = /** @type {?} */ ('DL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.DT = /** @type {?} */ ('DT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.EM = /** @type {?} */ ('EM');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLEmbedElement>} */
|
||||
goog.dom.TagName.EMBED = /** @type {?} */ ('EMBED');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLFieldSetElement>} */
|
||||
goog.dom.TagName.FIELDSET = /** @type {?} */ ('FIELDSET');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.FIGCAPTION = /** @type {?} */ ('FIGCAPTION');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.FIGURE = /** @type {?} */ ('FIGURE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLFontElement>} */
|
||||
goog.dom.TagName.FONT = /** @type {?} */ ('FONT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.FOOTER = /** @type {?} */ ('FOOTER');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLFormElement>} */
|
||||
goog.dom.TagName.FORM = /** @type {?} */ ('FORM');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLFrameElement>} */
|
||||
goog.dom.TagName.FRAME = /** @type {?} */ ('FRAME');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLFrameSetElement>} */
|
||||
goog.dom.TagName.FRAMESET = /** @type {?} */ ('FRAMESET');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H1 = /** @type {?} */ ('H1');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H2 = /** @type {?} */ ('H2');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H3 = /** @type {?} */ ('H3');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H4 = /** @type {?} */ ('H4');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H5 = /** @type {?} */ ('H5');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadingElement>} */
|
||||
goog.dom.TagName.H6 = /** @type {?} */ ('H6');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHeadElement>} */
|
||||
goog.dom.TagName.HEAD = /** @type {?} */ ('HEAD');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.HEADER = /** @type {?} */ ('HEADER');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.HGROUP = /** @type {?} */ ('HGROUP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHRElement>} */
|
||||
goog.dom.TagName.HR = /** @type {?} */ ('HR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLHtmlElement>} */
|
||||
goog.dom.TagName.HTML = /** @type {?} */ ('HTML');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.I = /** @type {?} */ ('I');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLIFrameElement>} */
|
||||
goog.dom.TagName.IFRAME = /** @type {?} */ ('IFRAME');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLImageElement>} */
|
||||
goog.dom.TagName.IMG = /** @type {?} */ ('IMG');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLInputElement>} */
|
||||
goog.dom.TagName.INPUT = /** @type {?} */ ('INPUT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLModElement>} */
|
||||
goog.dom.TagName.INS = /** @type {?} */ ('INS');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLIsIndexElement>} */
|
||||
goog.dom.TagName.ISINDEX = /** @type {?} */ ('ISINDEX');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.KBD = /** @type {?} */ ('KBD');
|
||||
|
||||
// HTMLKeygenElement is deprecated.
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.KEYGEN = /** @type {?} */ ('KEYGEN');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLLabelElement>} */
|
||||
goog.dom.TagName.LABEL = /** @type {?} */ ('LABEL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLLegendElement>} */
|
||||
goog.dom.TagName.LEGEND = /** @type {?} */ ('LEGEND');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLLIElement>} */
|
||||
goog.dom.TagName.LI = /** @type {?} */ ('LI');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLLinkElement>} */
|
||||
goog.dom.TagName.LINK = /** @type {?} */ ('LINK');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.MAIN = /** @type {?} */ ('MAIN');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLMapElement>} */
|
||||
goog.dom.TagName.MAP = /** @type {?} */ ('MAP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.MARK = /** @type {?} */ ('MARK');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.MATH = /** @type {?} */ ('MATH');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLMenuElement>} */
|
||||
goog.dom.TagName.MENU = /** @type {?} */ ('MENU');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLMenuItemElement>} */
|
||||
goog.dom.TagName.MENUITEM = /** @type {?} */ ('MENUITEM');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLMetaElement>} */
|
||||
goog.dom.TagName.META = /** @type {?} */ ('META');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLMeterElement>} */
|
||||
goog.dom.TagName.METER = /** @type {?} */ ('METER');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.NAV = /** @type {?} */ ('NAV');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.NOFRAMES = /** @type {?} */ ('NOFRAMES');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.NOSCRIPT = /** @type {?} */ ('NOSCRIPT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLObjectElement>} */
|
||||
goog.dom.TagName.OBJECT = /** @type {?} */ ('OBJECT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLOListElement>} */
|
||||
goog.dom.TagName.OL = /** @type {?} */ ('OL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLOptGroupElement>} */
|
||||
goog.dom.TagName.OPTGROUP = /** @type {?} */ ('OPTGROUP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLOptionElement>} */
|
||||
goog.dom.TagName.OPTION = /** @type {?} */ ('OPTION');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLOutputElement>} */
|
||||
goog.dom.TagName.OUTPUT = /** @type {?} */ ('OUTPUT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLParagraphElement>} */
|
||||
goog.dom.TagName.P = /** @type {?} */ ('P');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLParamElement>} */
|
||||
goog.dom.TagName.PARAM = /** @type {?} */ ('PARAM');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLPictureElement>} */
|
||||
goog.dom.TagName.PICTURE = /** @type {?} */ ('PICTURE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLPreElement>} */
|
||||
goog.dom.TagName.PRE = /** @type {?} */ ('PRE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLProgressElement>} */
|
||||
goog.dom.TagName.PROGRESS = /** @type {?} */ ('PROGRESS');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLQuoteElement>} */
|
||||
goog.dom.TagName.Q = /** @type {?} */ ('Q');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.RP = /** @type {?} */ ('RP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.RT = /** @type {?} */ ('RT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.RTC = /** @type {?} */ ('RTC');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.RUBY = /** @type {?} */ ('RUBY');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.S = /** @type {?} */ ('S');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SAMP = /** @type {?} */ ('SAMP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLScriptElement>} */
|
||||
goog.dom.TagName.SCRIPT = /** @type {?} */ ('SCRIPT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SECTION = /** @type {?} */ ('SECTION');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLSelectElement>} */
|
||||
goog.dom.TagName.SELECT = /** @type {?} */ ('SELECT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SMALL = /** @type {?} */ ('SMALL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLSourceElement>} */
|
||||
goog.dom.TagName.SOURCE = /** @type {?} */ ('SOURCE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLSpanElement>} */
|
||||
goog.dom.TagName.SPAN = /** @type {?} */ ('SPAN');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.STRIKE = /** @type {?} */ ('STRIKE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.STRONG = /** @type {?} */ ('STRONG');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLStyleElement>} */
|
||||
goog.dom.TagName.STYLE = /** @type {?} */ ('STYLE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SUB = /** @type {?} */ ('SUB');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SUMMARY = /** @type {?} */ ('SUMMARY');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SUP = /** @type {?} */ ('SUP');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.SVG = /** @type {?} */ ('SVG');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableElement>} */
|
||||
goog.dom.TagName.TABLE = /** @type {?} */ ('TABLE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableSectionElement>} */
|
||||
goog.dom.TagName.TBODY = /** @type {?} */ ('TBODY');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableCellElement>} */
|
||||
goog.dom.TagName.TD = /** @type {?} */ ('TD');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTemplateElement>} */
|
||||
goog.dom.TagName.TEMPLATE = /** @type {?} */ ('TEMPLATE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTextAreaElement>} */
|
||||
goog.dom.TagName.TEXTAREA = /** @type {?} */ ('TEXTAREA');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableSectionElement>} */
|
||||
goog.dom.TagName.TFOOT = /** @type {?} */ ('TFOOT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableCellElement>} */
|
||||
goog.dom.TagName.TH = /** @type {?} */ ('TH');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableSectionElement>} */
|
||||
goog.dom.TagName.THEAD = /** @type {?} */ ('THEAD');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.TIME = /** @type {?} */ ('TIME');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTitleElement>} */
|
||||
goog.dom.TagName.TITLE = /** @type {?} */ ('TITLE');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTableRowElement>} */
|
||||
goog.dom.TagName.TR = /** @type {?} */ ('TR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLTrackElement>} */
|
||||
goog.dom.TagName.TRACK = /** @type {?} */ ('TRACK');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.TT = /** @type {?} */ ('TT');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.U = /** @type {?} */ ('U');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLUListElement>} */
|
||||
goog.dom.TagName.UL = /** @type {?} */ ('UL');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.VAR = /** @type {?} */ ('VAR');
|
||||
|
||||
/** @const {!goog.dom.TagName<!HTMLVideoElement>} */
|
||||
goog.dom.TagName.VIDEO = /** @type {?} */ ('VIDEO');
|
||||
|
||||
/** @const {!goog.dom.TagName<!goog.dom.HtmlElement>} */
|
||||
goog.dom.TagName.WBR = /** @type {?} */ ('WBR');
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for HTML element tag names.
|
||||
*/
|
||||
goog.provide('goog.dom.tags');
|
||||
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* The void elements specified by
|
||||
* http://www.w3.org/TR/html-markup/syntax.html#void-elements.
|
||||
* @const @private {!Object<string, boolean>}
|
||||
*/
|
||||
goog.dom.tags.VOID_TAGS_ = goog.object.createSet(
|
||||
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
|
||||
'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr');
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the tag is void (with no contents allowed and no legal end
|
||||
* tag), for example 'br'.
|
||||
* @param {string} tagName The tag name in lower case.
|
||||
* @return {boolean}
|
||||
*/
|
||||
goog.dom.tags.isVoidTag = function(tagName) {
|
||||
'use strict';
|
||||
return goog.dom.tags.VOID_TAGS_[tagName] === true;
|
||||
};
|
||||
Executable
+447
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A patched, standardized event object for browser events.
|
||||
*
|
||||
* <pre>
|
||||
* The patched event object contains the following members:
|
||||
* - type {string} Event type, e.g. 'click'
|
||||
* - target {Object} The element that actually triggered the event
|
||||
* - currentTarget {Object} The element the listener is attached to
|
||||
* - relatedTarget {Object} For mouseover and mouseout, the previous object
|
||||
* - offsetX {number} X-coordinate relative to target
|
||||
* - offsetY {number} Y-coordinate relative to target
|
||||
* - clientX {number} X-coordinate relative to viewport
|
||||
* - clientY {number} Y-coordinate relative to viewport
|
||||
* - screenX {number} X-coordinate relative to the edge of the screen
|
||||
* - screenY {number} Y-coordinate relative to the edge of the screen
|
||||
* - button {number} Mouse button. Use isButton() to test.
|
||||
* - keyCode {number} Key-code
|
||||
* - ctrlKey {boolean} Was ctrl key depressed
|
||||
* - altKey {boolean} Was alt key depressed
|
||||
* - shiftKey {boolean} Was shift key depressed
|
||||
* - metaKey {boolean} Was meta key depressed
|
||||
* - pointerId {number} Pointer ID
|
||||
* - pointerType {string} Pointer type, e.g. 'mouse', 'pen', or 'touch'
|
||||
* - defaultPrevented {boolean} Whether the default action has been prevented
|
||||
* - state {Object} History state object
|
||||
*
|
||||
* NOTE: The keyCode member contains the raw browser keyCode. For normalized
|
||||
* key and character code use {@link goog.events.KeyHandler}.
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.BrowserEvent');
|
||||
goog.provide('goog.events.BrowserEvent.MouseButton');
|
||||
goog.provide('goog.events.BrowserEvent.PointerType');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.reflect');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, use the layerX and layerY properties of a native
|
||||
* browser event over the offsetX and offsetY properties, which cause expensive
|
||||
* reflow. If layerX or layerY is not defined, offsetX and offsetY will be used
|
||||
* as usual.
|
||||
*/
|
||||
goog.events.USE_LAYER_XY_AS_OFFSET_XY =
|
||||
goog.define('goog.events.USE_LAYER_XY_AS_OFFSET_XY', false);
|
||||
|
||||
/**
|
||||
* Accepts a browser event object and creates a patched, cross browser event
|
||||
* object.
|
||||
* The content of this object will not be initialized if no event object is
|
||||
* provided. If this is the case, init() needs to be invoked separately.
|
||||
* @param {Event=} opt_e Browser event object.
|
||||
* @param {EventTarget=} opt_currentTarget Current target for event.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
|
||||
'use strict';
|
||||
goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
|
||||
|
||||
/**
|
||||
* Target that fired the event.
|
||||
* @override
|
||||
* @type {?Node}
|
||||
*/
|
||||
this.target = null;
|
||||
|
||||
/**
|
||||
* Node that had the listener attached.
|
||||
* @override
|
||||
* @type {?Node|undefined}
|
||||
*/
|
||||
this.currentTarget = null;
|
||||
|
||||
/**
|
||||
* For mouseover and mouseout events, the related object for the event.
|
||||
* @type {?Node}
|
||||
*/
|
||||
this.relatedTarget = null;
|
||||
|
||||
/**
|
||||
* X-coordinate relative to target.
|
||||
* @type {number}
|
||||
*/
|
||||
this.offsetX = 0;
|
||||
|
||||
/**
|
||||
* Y-coordinate relative to target.
|
||||
* @type {number}
|
||||
*/
|
||||
this.offsetY = 0;
|
||||
|
||||
/**
|
||||
* X-coordinate relative to the window.
|
||||
* @type {number}
|
||||
*/
|
||||
this.clientX = 0;
|
||||
|
||||
/**
|
||||
* Y-coordinate relative to the window.
|
||||
* @type {number}
|
||||
*/
|
||||
this.clientY = 0;
|
||||
|
||||
/**
|
||||
* X-coordinate relative to the monitor.
|
||||
* @type {number}
|
||||
*/
|
||||
this.screenX = 0;
|
||||
|
||||
/**
|
||||
* Y-coordinate relative to the monitor.
|
||||
* @type {number}
|
||||
*/
|
||||
this.screenY = 0;
|
||||
|
||||
/**
|
||||
* Which mouse button was pressed.
|
||||
* @type {number}
|
||||
*/
|
||||
this.button = 0;
|
||||
|
||||
/**
|
||||
* Key of key press.
|
||||
* @type {string}
|
||||
*/
|
||||
this.key = '';
|
||||
|
||||
/**
|
||||
* Keycode of key press.
|
||||
* @type {number}
|
||||
*/
|
||||
this.keyCode = 0;
|
||||
|
||||
/**
|
||||
* Keycode of key press.
|
||||
* @type {number}
|
||||
*/
|
||||
this.charCode = 0;
|
||||
|
||||
/**
|
||||
* Whether control was pressed at time of event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.ctrlKey = false;
|
||||
|
||||
/**
|
||||
* Whether alt was pressed at time of event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.altKey = false;
|
||||
|
||||
/**
|
||||
* Whether shift was pressed at time of event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.shiftKey = false;
|
||||
|
||||
/**
|
||||
* Whether the meta key was pressed at time of event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.metaKey = false;
|
||||
|
||||
/**
|
||||
* History state object, only set for PopState events where it's a copy of the
|
||||
* state object provided to pushState or replaceState.
|
||||
* @type {?Object}
|
||||
*/
|
||||
this.state = null;
|
||||
|
||||
/**
|
||||
* Whether the default platform modifier key was pressed at time of event.
|
||||
* (This is control for all platforms except Mac, where it's Meta.)
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.platformModifierKey = false;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.pointerId = 0;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.pointerType = '';
|
||||
|
||||
/**
|
||||
* The browser event object.
|
||||
* @private {?Event}
|
||||
*/
|
||||
this.event_ = null;
|
||||
|
||||
if (opt_e) {
|
||||
this.init(opt_e, opt_currentTarget);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.events.BrowserEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Normalized button constants for the mouse.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.events.BrowserEvent.MouseButton = {
|
||||
LEFT: 0,
|
||||
MIDDLE: 1,
|
||||
RIGHT: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalized pointer type constants for pointer events.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.events.BrowserEvent.PointerType = {
|
||||
MOUSE: 'mouse',
|
||||
PEN: 'pen',
|
||||
TOUCH: 'touch'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static data for mapping mouse buttons.
|
||||
* @type {!Array<number>}
|
||||
* @deprecated Use `goog.events.BrowserEvent.IE_BUTTON_MAP` instead.
|
||||
*/
|
||||
goog.events.BrowserEvent.IEButtonMap = goog.debug.freeze([
|
||||
1, // LEFT
|
||||
4, // MIDDLE
|
||||
2 // RIGHT
|
||||
]);
|
||||
|
||||
|
||||
/**
|
||||
* Static data for mapping mouse buttons.
|
||||
* @const {!Array<number>}
|
||||
*/
|
||||
goog.events.BrowserEvent.IE_BUTTON_MAP = goog.events.BrowserEvent.IEButtonMap;
|
||||
|
||||
|
||||
/**
|
||||
* Static data for mapping MSPointerEvent types to PointerEvent types.
|
||||
* @const {!Object<number, goog.events.BrowserEvent.PointerType>}
|
||||
*/
|
||||
goog.events.BrowserEvent.IE_POINTER_TYPE_MAP = goog.debug.freeze({
|
||||
2: goog.events.BrowserEvent.PointerType.TOUCH,
|
||||
3: goog.events.BrowserEvent.PointerType.PEN,
|
||||
4: goog.events.BrowserEvent.PointerType.MOUSE
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a browser event object and creates a patched, cross browser event
|
||||
* object.
|
||||
* @param {Event} e Browser event object.
|
||||
* @param {EventTarget=} opt_currentTarget Current target for event.
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
|
||||
'use strict';
|
||||
var type = this.type = e.type;
|
||||
|
||||
/**
|
||||
* On touch devices use the first "changed touch" as the relevant touch.
|
||||
* @type {?Touch}
|
||||
*/
|
||||
var relevantTouch =
|
||||
e.changedTouches && e.changedTouches.length ? e.changedTouches[0] : null;
|
||||
|
||||
// TODO(nicksantos): Change this.target to type EventTarget.
|
||||
this.target = /** @type {Node} */ (e.target) || e.srcElement;
|
||||
|
||||
// TODO(nicksantos): Change this.currentTarget to type EventTarget.
|
||||
this.currentTarget = /** @type {Node} */ (opt_currentTarget);
|
||||
|
||||
var relatedTarget = /** @type {Node} */ (e.relatedTarget);
|
||||
if (relatedTarget) {
|
||||
// There's a bug in FireFox where sometimes, relatedTarget will be a
|
||||
// chrome element, and accessing any property of it will get a permission
|
||||
// denied exception. See:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=497780
|
||||
if (goog.userAgent.GECKO) {
|
||||
if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
|
||||
relatedTarget = null;
|
||||
}
|
||||
}
|
||||
} else if (type == goog.events.EventType.MOUSEOVER) {
|
||||
relatedTarget = e.fromElement;
|
||||
} else if (type == goog.events.EventType.MOUSEOUT) {
|
||||
relatedTarget = e.toElement;
|
||||
}
|
||||
|
||||
this.relatedTarget = relatedTarget;
|
||||
|
||||
if (relevantTouch) {
|
||||
this.clientX = relevantTouch.clientX !== undefined ? relevantTouch.clientX :
|
||||
relevantTouch.pageX;
|
||||
this.clientY = relevantTouch.clientY !== undefined ? relevantTouch.clientY :
|
||||
relevantTouch.pageY;
|
||||
this.screenX = relevantTouch.screenX || 0;
|
||||
this.screenY = relevantTouch.screenY || 0;
|
||||
} else {
|
||||
if (goog.events.USE_LAYER_XY_AS_OFFSET_XY) {
|
||||
this.offsetX = (e.layerX !== undefined) ? e.layerX : e.offsetX;
|
||||
this.offsetY = (e.layerY !== undefined) ? e.layerY : e.offsetY;
|
||||
} else {
|
||||
// Webkit emits a lame warning whenever layerX/layerY is accessed.
|
||||
// http://code.google.com/p/chromium/issues/detail?id=101733
|
||||
this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
|
||||
e.offsetX :
|
||||
e.layerX;
|
||||
this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
|
||||
e.offsetY :
|
||||
e.layerY;
|
||||
}
|
||||
this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
|
||||
this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
|
||||
this.screenX = e.screenX || 0;
|
||||
this.screenY = e.screenY || 0;
|
||||
}
|
||||
|
||||
this.button = e.button;
|
||||
|
||||
this.keyCode = e.keyCode || 0;
|
||||
this.key = e.key || '';
|
||||
this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
|
||||
this.ctrlKey = e.ctrlKey;
|
||||
this.altKey = e.altKey;
|
||||
this.shiftKey = e.shiftKey;
|
||||
this.metaKey = e.metaKey;
|
||||
this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
|
||||
this.pointerId = e.pointerId || 0;
|
||||
this.pointerType = goog.events.BrowserEvent.getPointerType_(e);
|
||||
this.state = e.state;
|
||||
this.event_ = e;
|
||||
if (e.defaultPrevented) {
|
||||
// Sync native event state to internal state via super class, where default
|
||||
// prevention is implemented and managed.
|
||||
goog.events.BrowserEvent.superClass_.preventDefault.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests to see which button was pressed during the event. This is really only
|
||||
* useful in IE and Gecko browsers. And in IE, it's only useful for
|
||||
* mousedown/mouseup events, because click only fires for the left mouse button.
|
||||
*
|
||||
* Safari 2 only reports the left button being clicked, and uses the value '1'
|
||||
* instead of 0. Opera only reports a mousedown event for the middle button, and
|
||||
* no mouse events for the right button. Opera has default behavior for left and
|
||||
* middle click that can only be overridden via a configuration setting.
|
||||
*
|
||||
* There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
|
||||
*
|
||||
* @param {goog.events.BrowserEvent.MouseButton} button The button
|
||||
* to test for.
|
||||
* @return {boolean} True if button was pressed.
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.isButton = function(button) {
|
||||
'use strict';
|
||||
return this.event_.button == button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether this has an "action"-producing mouse button.
|
||||
*
|
||||
* By definition, this includes left-click on windows/linux, and left-click
|
||||
* without the ctrl key on Macs.
|
||||
*
|
||||
* @return {boolean} The result.
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
|
||||
'use strict';
|
||||
// Ctrl+click should never behave like a left-click on mac, regardless of
|
||||
// whether or not the browser will actually ever emit such an event. If
|
||||
// we see it, treat it like right-click always.
|
||||
return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
|
||||
!(goog.userAgent.MAC && this.ctrlKey);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.stopPropagation = function() {
|
||||
'use strict';
|
||||
goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
|
||||
if (this.event_.stopPropagation) {
|
||||
this.event_.stopPropagation();
|
||||
} else {
|
||||
this.event_.cancelBubble = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.preventDefault = function() {
|
||||
'use strict';
|
||||
goog.events.BrowserEvent.superClass_.preventDefault.call(this);
|
||||
var be = this.event_;
|
||||
if (!be.preventDefault) {
|
||||
be.returnValue = false;
|
||||
} else {
|
||||
be.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Event} The underlying browser event object.
|
||||
*/
|
||||
goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
|
||||
'use strict';
|
||||
return this.event_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the pointer type from the given event.
|
||||
* @param {!Event} e
|
||||
* @return {string} The pointer type, e.g. 'mouse', 'pen', or 'touch'.
|
||||
* @private
|
||||
*/
|
||||
goog.events.BrowserEvent.getPointerType_ = function(e) {
|
||||
'use strict';
|
||||
if (typeof (e.pointerType) === 'string') {
|
||||
return e.pointerType;
|
||||
}
|
||||
// IE10 uses integer codes for pointer type.
|
||||
// https://msdn.microsoft.com/en-us/library/hh772359(v=vs.85).aspx
|
||||
return goog.events.BrowserEvent.IE_POINTER_TYPE_MAP[e.pointerType] || '';
|
||||
};
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Browser capability checks for the events package.
|
||||
*/
|
||||
|
||||
goog.module('goog.events.BrowserFeature');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
|
||||
/**
|
||||
* Tricks Closure Compiler into believing that a function is pure. The compiler
|
||||
* assumes that any `valueOf` function is pure, without analyzing its contents.
|
||||
*
|
||||
* @param {function(): T} fn
|
||||
* @return {T}
|
||||
* @template T
|
||||
*/
|
||||
const purify = (fn) => {
|
||||
return ({valueOf: fn}).valueOf();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enum of browser capabilities.
|
||||
* @enum {boolean}
|
||||
*/
|
||||
exports = {
|
||||
/**
|
||||
* Whether the button attribute of the event is W3C compliant. False in
|
||||
* Internet Explorer prior to version 9; document-version dependent.
|
||||
*/
|
||||
HAS_W3C_BUTTON: true,
|
||||
|
||||
/**
|
||||
* Whether the browser supports full W3C event model.
|
||||
*/
|
||||
HAS_W3C_EVENT_SUPPORT: true,
|
||||
|
||||
/**
|
||||
* To prevent default in IE7-8 for certain keydown events we need set the
|
||||
* keyCode to -1.
|
||||
*/
|
||||
SET_KEY_CODE_TO_PREVENT_DEFAULT: false,
|
||||
|
||||
/**
|
||||
* Whether the `navigator.onLine` property is supported.
|
||||
*/
|
||||
HAS_NAVIGATOR_ONLINE_PROPERTY: true,
|
||||
|
||||
/**
|
||||
* Whether HTML5 network online/offline events are supported.
|
||||
*/
|
||||
HAS_HTML5_NETWORK_EVENT_SUPPORT: true,
|
||||
|
||||
/**
|
||||
* Whether HTML5 network events fire on document.body, or otherwise the
|
||||
* window.
|
||||
*/
|
||||
HTML5_NETWORK_EVENTS_FIRE_ON_BODY: false,
|
||||
|
||||
/**
|
||||
* Whether touch is enabled in the browser.
|
||||
*/
|
||||
TOUCH_ENABLED:
|
||||
('ontouchstart' in goog.global ||
|
||||
!!(goog.global['document'] && document.documentElement &&
|
||||
'ontouchstart' in document.documentElement) ||
|
||||
// IE10 uses non-standard touch events, so it has a different check.
|
||||
!!(goog.global['navigator'] &&
|
||||
(goog.global['navigator']['maxTouchPoints'] ||
|
||||
goog.global['navigator']['msMaxTouchPoints']))),
|
||||
|
||||
/**
|
||||
* Whether addEventListener supports W3C standard pointer events.
|
||||
* http://www.w3.org/TR/pointerevents/
|
||||
*/
|
||||
POINTER_EVENTS: ('PointerEvent' in goog.global),
|
||||
|
||||
/**
|
||||
* Whether addEventListener supports MSPointer events (only used in IE10).
|
||||
* http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
|
||||
* http://msdn.microsoft.com/library/hh673557(v=vs.85).aspx
|
||||
*/
|
||||
MSPOINTER_EVENTS:
|
||||
('MSPointerEvent' in goog.global &&
|
||||
!!(goog.global['navigator'] &&
|
||||
goog.global['navigator']['msPointerEnabled'])),
|
||||
|
||||
/**
|
||||
* Whether addEventListener supports {passive: true}.
|
||||
* https://developers.google.com/web/updates/2016/06/passive-event-listeners
|
||||
*/
|
||||
PASSIVE_EVENTS: purify(function() {
|
||||
// If we're in a web worker or other custom environment, we can't tell.
|
||||
if (!goog.global.addEventListener || !Object.defineProperty) { // IE 8
|
||||
return false;
|
||||
}
|
||||
|
||||
var passive = false;
|
||||
var options = Object.defineProperty({}, 'passive', {
|
||||
get: function() {
|
||||
passive = true;
|
||||
}
|
||||
});
|
||||
try {
|
||||
goog.global.addEventListener('test', goog.nullFunction, options);
|
||||
goog.global.removeEventListener('test', goog.nullFunction, options);
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
return passive;
|
||||
})
|
||||
};
|
||||
Executable
+125
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A base class for event objects.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.events.Event');
|
||||
|
||||
/**
|
||||
* goog.events.Event no longer depends on goog.Disposable. Keep requiring
|
||||
* goog.Disposable here to not break projects which assume this dependency.
|
||||
* @suppress {extraRequire}
|
||||
*/
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.events.EventId');
|
||||
|
||||
|
||||
/**
|
||||
* A base class for event objects, so that they can support preventDefault and
|
||||
* stopPropagation.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type Event Type.
|
||||
* @param {Object=} opt_target Reference to the object that is the target of
|
||||
* this event. It has to implement the `EventTarget` interface
|
||||
* declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
|
||||
* @constructor
|
||||
*/
|
||||
goog.events.Event = function(type, opt_target) {
|
||||
'use strict';
|
||||
/**
|
||||
* Event type.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = type instanceof goog.events.EventId ? String(type) : type;
|
||||
|
||||
/**
|
||||
* TODO(tbreisacher): The type should probably be
|
||||
* EventTarget|goog.events.EventTarget.
|
||||
*
|
||||
* Target of the event.
|
||||
* @type {Object|undefined}
|
||||
*/
|
||||
this.target = opt_target;
|
||||
|
||||
/**
|
||||
* Object that had the listener attached.
|
||||
* @type {Object|undefined}
|
||||
*/
|
||||
this.currentTarget = this.target;
|
||||
|
||||
/**
|
||||
* Whether to cancel the event in internal capture/bubble processing for IE.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.propagationStopped_ = false;
|
||||
|
||||
/**
|
||||
* Whether the default action has been prevented.
|
||||
* This is a property to match the W3C specification at
|
||||
* {@link http://www.w3.org/TR/DOM-Level-3-Events/
|
||||
* #events-event-type-defaultPrevented}.
|
||||
* Must be treated as read-only outside the class.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.defaultPrevented = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} true iff internal propagation has been stopped.
|
||||
*/
|
||||
goog.events.Event.prototype.hasPropagationStopped = function() {
|
||||
'use strict';
|
||||
return this.propagationStopped_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops event propagation.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.events.Event.prototype.stopPropagation = function() {
|
||||
'use strict';
|
||||
this.propagationStopped_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prevents the default action, for example a link redirecting to a url.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.events.Event.prototype.preventDefault = function() {
|
||||
'use strict';
|
||||
this.defaultPrevented = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the propagation of the event. It is equivalent to
|
||||
* `e.stopPropagation()`, but can be used as the callback argument of
|
||||
* {@link goog.events.listen} without declaring another function.
|
||||
* @param {!goog.events.Event} e An event.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.events.Event.stopPropagation = function(e) {
|
||||
'use strict';
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prevents the default action. It is equivalent to
|
||||
* `e.preventDefault()`, but can be used as the callback argument of
|
||||
* {@link goog.events.listen} without declaring another function.
|
||||
* @param {!goog.events.Event} e An event.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.events.Event.preventDefault = function(e) {
|
||||
'use strict';
|
||||
e.preventDefault();
|
||||
};
|
||||
Executable
+488
@@ -0,0 +1,488 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class to create objects which want to handle multiple events
|
||||
* and have their listeners easily cleaned up via a dispose method.
|
||||
*
|
||||
* Example:
|
||||
* <pre>
|
||||
* function Something() {
|
||||
* Something.base(this);
|
||||
*
|
||||
* ... set up object ...
|
||||
*
|
||||
* // Add event listeners
|
||||
* this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
|
||||
* this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
|
||||
* this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
|
||||
* this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
|
||||
* this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
|
||||
* }
|
||||
* goog.inherits(Something, goog.events.EventHandler);
|
||||
*
|
||||
* Something.prototype.disposeInternal = function() {
|
||||
* Something.base(this, 'disposeInternal');
|
||||
* goog.dom.removeNode(this.container);
|
||||
* };
|
||||
*
|
||||
*
|
||||
* // Then elsewhere:
|
||||
*
|
||||
* var activeSomething = null;
|
||||
* function openSomething() {
|
||||
* activeSomething = new Something();
|
||||
* }
|
||||
*
|
||||
* function closeSomething() {
|
||||
* if (activeSomething) {
|
||||
* activeSomething.dispose(); // Remove event listeners
|
||||
* activeSomething = null;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.EventHandler');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.object');
|
||||
goog.requireType('goog.events.Event');
|
||||
goog.requireType('goog.events.EventId');
|
||||
goog.requireType('goog.events.EventTarget');
|
||||
goog.requireType('goog.events.EventWrapper');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Super class for objects that want to easily manage a number of event
|
||||
* listeners. It allows a short cut to listen and also provides a quick way
|
||||
* to remove all events listeners belonging to this object.
|
||||
* @param {SCOPE=} opt_scope Object in whose scope to call the listeners.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.events.EventHandler = function(opt_scope) {
|
||||
'use strict';
|
||||
goog.Disposable.call(this);
|
||||
// TODO(mknichel): Rename this to this.scope_ and fix the classes in google3
|
||||
// that access this private variable. :(
|
||||
this.handler_ = opt_scope;
|
||||
|
||||
/**
|
||||
* Keys for events that are being listened to.
|
||||
* @type {!Object<!goog.events.Key>}
|
||||
* @private
|
||||
*/
|
||||
this.keys_ = {};
|
||||
};
|
||||
goog.inherits(goog.events.EventHandler, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Utility array used to unify the cases of listening for an array of types
|
||||
* and listening for a single event, without using recursion or allocating
|
||||
* an array each time.
|
||||
* @type {!Array<string>}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventHandler.typeArray_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted then the
|
||||
* EventHandler's handleEvent method will be used.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
|
||||
* opt_fn Optional callback function to be used as the listener or an object
|
||||
* with handleEvent function.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template EVENTOBJ, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listen = function(
|
||||
src, type, opt_fn, opt_options) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
return self.listen_(src, type, opt_fn, opt_options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted then the
|
||||
* EventHandler's handleEvent method will be used.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
|
||||
* null|undefined} fn Optional callback function to be used as the
|
||||
* listener or an object with handleEvent function.
|
||||
* @param {boolean|!AddEventListenerOptions|undefined} options
|
||||
* @param {T} scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template T, EVENTOBJ, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenWithScope = function(
|
||||
src, type, fn, options, scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
// TODO(mknichel): Deprecate this function.
|
||||
return self.listen_(src, type, fn, options, scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted then the
|
||||
* EventHandler's handleEvent method will be used.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
|
||||
* Optional callback function to be used as the listener or an object with
|
||||
* handleEvent function.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @param {Object=} opt_scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template EVENTOBJ, THIS
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listen_ = function(
|
||||
src, type, opt_fn, opt_options, opt_scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
if (!Array.isArray(type)) {
|
||||
if (type) {
|
||||
goog.events.EventHandler.typeArray_[0] = type.toString();
|
||||
}
|
||||
type = goog.events.EventHandler.typeArray_;
|
||||
}
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
var listenerObj = goog.events.listen(
|
||||
src, type[i], opt_fn || self.handleEvent, opt_options || false,
|
||||
opt_scope || self.handler_ || self);
|
||||
|
||||
if (!listenerObj) {
|
||||
// When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
|
||||
// (goog.events.CaptureSimulationMode) in IE8-, it will return null
|
||||
// value.
|
||||
return self;
|
||||
}
|
||||
|
||||
var key = listenerObj.key;
|
||||
self.keys_[key] = listenerObj;
|
||||
}
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted, then the
|
||||
* EventHandler's handleEvent method will be used. After the event has fired the
|
||||
* event listener is removed from the target. If an array of event types is
|
||||
* provided, each event type will be listened to once.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
|
||||
* opt_fn
|
||||
* Optional callback function to be used as the listener or an object with
|
||||
* handleEvent function.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template EVENTOBJ, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenOnce = function(
|
||||
src, type, opt_fn, opt_options) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
return self.listenOnce_(src, type, opt_fn, opt_options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted, then the
|
||||
* EventHandler's handleEvent method will be used. After the event has fired the
|
||||
* event listener is removed from the target. If an array of event types is
|
||||
* provided, each event type will be listened to once.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
|
||||
* null|undefined} fn Optional callback function to be used as the
|
||||
* listener or an object with handleEvent function.
|
||||
* @param {boolean|undefined} capture Optional whether to use capture phase.
|
||||
* @param {T} scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template T, EVENTOBJ, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenOnceWithScope = function(
|
||||
src, type, fn, capture, scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
// TODO(mknichel): Deprecate this function.
|
||||
return self.listenOnce_(src, type, fn, capture, scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listen to an event on a Listenable. If the function is omitted, then the
|
||||
* EventHandler's handleEvent method will be used. After the event has fired
|
||||
* the event listener is removed from the target. If an array of event types is
|
||||
* provided, each event type will be listened to once.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type to listen for or array of event types.
|
||||
* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
|
||||
* Optional callback function to be used as the listener or an object with
|
||||
* handleEvent function.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @param {Object=} opt_scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template EVENTOBJ, THIS
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenOnce_ = function(
|
||||
src, type, opt_fn, opt_options, opt_scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
self.listenOnce_(src, type[i], opt_fn, opt_options, opt_scope);
|
||||
}
|
||||
} else {
|
||||
var listenerObj = goog.events.listenOnce(
|
||||
src, type, opt_fn || self.handleEvent, opt_options,
|
||||
opt_scope || self.handler_ || self);
|
||||
if (!listenerObj) {
|
||||
// When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
|
||||
// (goog.events.CaptureSimulationMode) in IE8-, it will return null
|
||||
// value.
|
||||
return self;
|
||||
}
|
||||
|
||||
var key = listenerObj.key;
|
||||
self.keys_[key] = listenerObj;
|
||||
}
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener with a specific event wrapper on a DOM Node or an
|
||||
* object that has implemented {@link goog.events.EventTarget}. A listener can
|
||||
* only be added once to an object.
|
||||
*
|
||||
* @param {EventTarget|goog.events.EventTarget} src The node to listen to
|
||||
* events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener
|
||||
* Callback method, or an object with a handleEvent function.
|
||||
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
|
||||
* false).
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenWithWrapper = function(
|
||||
src, wrapper, listener, opt_capt) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
// TODO(mknichel): Remove the opt_scope from this function and then
|
||||
// templatize it.
|
||||
return self.listenWithWrapper_(src, wrapper, listener, opt_capt);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener with a specific event wrapper on a DOM Node or an
|
||||
* object that has implemented {@link goog.events.EventTarget}. A listener can
|
||||
* only be added once to an object.
|
||||
*
|
||||
* @param {EventTarget|goog.events.EventTarget} src The node to listen to
|
||||
* events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}
|
||||
* listener Optional callback function to be used as the
|
||||
* listener or an object with handleEvent function.
|
||||
* @param {boolean|undefined} capture Optional whether to use capture phase.
|
||||
* @param {T} scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template T, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
|
||||
src, wrapper, listener, capture, scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
// TODO(mknichel): Deprecate this function.
|
||||
return self.listenWithWrapper_(src, wrapper, listener, capture, scope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener with a specific event wrapper on a DOM Node or an
|
||||
* object that has implemented {@link goog.events.EventTarget}. A listener can
|
||||
* only be added once to an object.
|
||||
*
|
||||
* @param {EventTarget|goog.events.EventTarget} src The node to listen to
|
||||
* events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
|
||||
* method, or an object with a handleEvent function.
|
||||
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
|
||||
* false).
|
||||
* @param {Object=} opt_scope Element in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template THIS
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventHandler.prototype.listenWithWrapper_ = function(
|
||||
src, wrapper, listener, opt_capt, opt_scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
wrapper.listen(
|
||||
src, listener, opt_capt, opt_scope || self.handler_ || self, self);
|
||||
return self;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} Number of listeners registered by this handler.
|
||||
*/
|
||||
goog.events.EventHandler.prototype.getListenerCount = function() {
|
||||
'use strict';
|
||||
var count = 0;
|
||||
for (var key in this.keys_) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unlistens on an event.
|
||||
* @param {goog.events.ListenableType} src Event source.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type or array of event types to unlisten to.
|
||||
* @param {function(this:?, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
|
||||
* opt_fn Optional callback function to be used as the listener or an object
|
||||
* with handleEvent function.
|
||||
* @param {(boolean|!EventListenerOptions)=} opt_options
|
||||
* @param {Object=} opt_scope Object in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template EVENTOBJ, THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.unlisten = function(
|
||||
src, type, opt_fn, opt_options, opt_scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
self.unlisten(src, type[i], opt_fn, opt_options, opt_scope);
|
||||
}
|
||||
} else {
|
||||
var capture =
|
||||
goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
|
||||
var listener = goog.events.getListener(
|
||||
src, type, opt_fn || self.handleEvent, capture,
|
||||
opt_scope || self.handler_ || self);
|
||||
|
||||
if (listener) {
|
||||
goog.events.unlistenByKey(listener);
|
||||
delete self.keys_[listener.key];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listenWithWrapper().
|
||||
*
|
||||
* @param {EventTarget|goog.events.EventTarget} src The target to stop
|
||||
* listening to events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener The
|
||||
* listener function to remove.
|
||||
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
|
||||
* whether the listener is fired during the capture or bubble phase of the
|
||||
* event.
|
||||
* @param {Object=} opt_scope Element in whose scope to call the listener.
|
||||
* @return {THIS} This object, allowing for chaining of calls.
|
||||
* @this {THIS}
|
||||
* @template THIS
|
||||
*/
|
||||
goog.events.EventHandler.prototype.unlistenWithWrapper = function(
|
||||
src, wrapper, listener, opt_capt, opt_scope) {
|
||||
'use strict';
|
||||
var self = /** @type {!goog.events.EventHandler} */ (this);
|
||||
wrapper.unlisten(
|
||||
src, listener, opt_capt, opt_scope || self.handler_ || self, self);
|
||||
return self;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unlistens to all events.
|
||||
*/
|
||||
goog.events.EventHandler.prototype.removeAll = function() {
|
||||
'use strict';
|
||||
goog.object.forEach(this.keys_, function(listenerObj, key) {
|
||||
'use strict';
|
||||
if (this.keys_.hasOwnProperty(key)) {
|
||||
goog.events.unlistenByKey(listenerObj);
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.keys_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of this EventHandler and removes all listeners that it registered.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.events.EventHandler.prototype.disposeInternal = function() {
|
||||
'use strict';
|
||||
goog.events.EventHandler.superClass_.disposeInternal.call(this);
|
||||
this.removeAll();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default event handler
|
||||
* @param {goog.events.Event} e Event object.
|
||||
*/
|
||||
goog.events.EventHandler.prototype.handleEvent = function(e) {
|
||||
'use strict';
|
||||
throw new Error('EventHandler.handleEvent not implemented');
|
||||
};
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.EventId');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A templated class that is used when registering for events. Typical usage:
|
||||
*
|
||||
* /** @type {goog.events.EventId<MyEventObj>} *\
|
||||
* var myEventId = new goog.events.EventId(
|
||||
* goog.events.getUniqueId(('someEvent'));
|
||||
*
|
||||
* // No need to cast or declare here since the compiler knows the
|
||||
* // correct type of 'evt' (MyEventObj).
|
||||
* something.listen(myEventId, function(evt) {});
|
||||
*
|
||||
* @param {string} eventId
|
||||
* @template T
|
||||
* @constructor
|
||||
* @struct
|
||||
* @final
|
||||
*/
|
||||
goog.events.EventId = function(eventId) {
|
||||
'use strict';
|
||||
/** @const */ this.id = eventId;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {string}
|
||||
*/
|
||||
goog.events.EventId.prototype.toString = function() {
|
||||
'use strict';
|
||||
return this.id;
|
||||
};
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A typedef for event like objects that are dispatchable via the
|
||||
* goog.events.dispatchEvent function.
|
||||
*/
|
||||
goog.provide('goog.events.EventLike');
|
||||
|
||||
goog.requireType('goog.events.Event');
|
||||
goog.requireType('goog.events.EventId');
|
||||
|
||||
/**
|
||||
* A typedef for event like objects that are dispatchable via the
|
||||
* goog.events.dispatchEvent function. strings are treated as the type for a
|
||||
* goog.events.Event. Objects are treated as an extension of a new
|
||||
* goog.events.Event with the type property of the object being used as the type
|
||||
* of the Event.
|
||||
* @typedef {string|Object|goog.events.Event|goog.events.EventId}
|
||||
*/
|
||||
goog.events.EventLike;
|
||||
Executable
+960
@@ -0,0 +1,960 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An event manager for both native browser event
|
||||
* targets and custom JavaScript event targets
|
||||
* (`goog.events.Listenable`). This provides an abstraction
|
||||
* over browsers' event systems.
|
||||
*
|
||||
* It also provides a simulation of W3C event model's capture phase in
|
||||
* Internet Explorer (IE 8 and below). Caveat: the simulation does not
|
||||
* interact well with listeners registered directly on the elements
|
||||
* (bypassing goog.events) or even with listeners registered via
|
||||
* goog.events in a separate JS binary. In these cases, we provide
|
||||
* no ordering guarantees.
|
||||
*
|
||||
* The listeners will receive a "patched" event object. Such event object
|
||||
* contains normalized values for certain event properties that differs in
|
||||
* different browsers.
|
||||
*
|
||||
* Example usage:
|
||||
* <pre>
|
||||
* goog.events.listen(myNode, 'click', function(e) { alert('woo') });
|
||||
* goog.events.listen(myNode, 'mouseover', mouseHandler, true);
|
||||
* goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
|
||||
* goog.events.removeAll(myNode);
|
||||
* </pre>
|
||||
*
|
||||
* in IE and event object patching]
|
||||
*
|
||||
* @see ../demos/events.html
|
||||
* @see ../demos/event-propagation.html
|
||||
* @see ../demos/stopevent.html
|
||||
*/
|
||||
|
||||
// IMPLEMENTATION NOTES:
|
||||
// goog.events stores an auxiliary data structure on each EventTarget
|
||||
// source being listened on. This allows us to take advantage of GC,
|
||||
// having the data structure GC'd when the EventTarget is GC'd. This
|
||||
// GC behavior is equivalent to using W3C DOM Events directly.
|
||||
|
||||
goog.provide('goog.events');
|
||||
goog.provide('goog.events.CaptureSimulationMode');
|
||||
goog.provide('goog.events.Key');
|
||||
goog.provide('goog.events.ListenableType');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.events.BrowserEvent');
|
||||
goog.require('goog.events.BrowserFeature');
|
||||
goog.require('goog.events.Listenable');
|
||||
goog.require('goog.events.ListenerMap');
|
||||
goog.requireType('goog.debug.ErrorHandler');
|
||||
goog.requireType('goog.events.EventId');
|
||||
goog.requireType('goog.events.EventLike');
|
||||
goog.requireType('goog.events.EventWrapper');
|
||||
goog.requireType('goog.events.ListenableKey');
|
||||
goog.requireType('goog.events.Listener');
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {number|goog.events.ListenableKey}
|
||||
*/
|
||||
goog.events.Key;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {EventTarget|goog.events.Listenable}
|
||||
*/
|
||||
goog.events.ListenableType;
|
||||
|
||||
|
||||
/**
|
||||
* Property name on a native event target for the listener map
|
||||
* associated with the event target.
|
||||
* @private @const {string}
|
||||
*/
|
||||
goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
|
||||
|
||||
|
||||
/**
|
||||
* String used to prepend to IE event types.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.events.onString_ = 'on';
|
||||
|
||||
|
||||
/**
|
||||
* Map of computed "on<eventname>" strings for IE event types. Caching
|
||||
* this removes an extra object allocation in goog.events.listen which
|
||||
* improves IE6 performance.
|
||||
* @const
|
||||
* @dict
|
||||
* @private
|
||||
*/
|
||||
goog.events.onStringMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number} Different capture simulation mode for IE8-.
|
||||
*/
|
||||
goog.events.CaptureSimulationMode = {
|
||||
/**
|
||||
* Does not perform capture simulation. Will asserts in IE8- when you
|
||||
* add capture listeners.
|
||||
*/
|
||||
OFF_AND_FAIL: 0,
|
||||
|
||||
/**
|
||||
* Does not perform capture simulation, silently ignore capture
|
||||
* listeners.
|
||||
*/
|
||||
OFF_AND_SILENT: 1,
|
||||
|
||||
/**
|
||||
* Performs capture simulation.
|
||||
*/
|
||||
ON: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {number} The capture simulation mode for IE8-. By default,
|
||||
* this is ON.
|
||||
*/
|
||||
goog.events.CAPTURE_SIMULATION_MODE =
|
||||
goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
|
||||
|
||||
|
||||
/**
|
||||
* Estimated count of total native listeners.
|
||||
* @private {number}
|
||||
*/
|
||||
goog.events.listenerCountEstimate_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener for a specific event on a native event
|
||||
* target (such as a DOM element) or an object that has implemented
|
||||
* {@link goog.events.Listenable}. A listener can only be added once
|
||||
* to an object and if it is added again the key for the listener is
|
||||
* returned. Note that if the existing listener is a one-off listener
|
||||
* (registered via listenOnce), it will no longer be a one-off
|
||||
* listener after a call to listen().
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The node to listen
|
||||
* to events on.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type or array of event types.
|
||||
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
|
||||
* listener Callback method, or an object with a handleEvent function.
|
||||
* WARNING: passing an Object is now softly deprecated.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @param {T=} opt_handler Element in whose scope to call the listener.
|
||||
* @return {goog.events.Key} Unique key for the listener.
|
||||
* @template T,EVENTOBJ
|
||||
*/
|
||||
goog.events.listen = function(src, type, listener, opt_options, opt_handler) {
|
||||
'use strict';
|
||||
if (opt_options && opt_options.once) {
|
||||
return goog.events.listenOnce(
|
||||
src, type, listener, opt_options, opt_handler);
|
||||
}
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
goog.events.listen(src, type[i], listener, opt_options, opt_handler);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
listener = goog.events.wrapListener(listener);
|
||||
if (goog.events.Listenable.isImplementedBy(src)) {
|
||||
var capture =
|
||||
goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
|
||||
return src.listen(
|
||||
/** @type {string|!goog.events.EventId} */ (type), listener, capture,
|
||||
opt_handler);
|
||||
} else {
|
||||
return goog.events.listen_(
|
||||
/** @type {!EventTarget} */ (src), type, listener,
|
||||
/* callOnce */ false, opt_options, opt_handler);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener for a specific event on a native event
|
||||
* target. A listener can only be added once to an object and if it
|
||||
* is added again the key for the listener is returned.
|
||||
*
|
||||
* Note that a one-off listener will not change an existing listener,
|
||||
* if any. On the other hand a normal listener will change existing
|
||||
* one-off listener to become a normal listener.
|
||||
*
|
||||
* @param {EventTarget} src The node to listen to events on.
|
||||
* @param {string|?goog.events.EventId<EVENTOBJ>} type Event type.
|
||||
* @param {!Function} listener Callback function.
|
||||
* @param {boolean} callOnce Whether the listener is a one-off
|
||||
* listener or otherwise.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @param {Object=} opt_handler Element in whose scope to call the listener.
|
||||
* @return {goog.events.ListenableKey} Unique key for the listener.
|
||||
* @template EVENTOBJ
|
||||
* @private
|
||||
*/
|
||||
goog.events.listen_ = function(
|
||||
src, type, listener, callOnce, opt_options, opt_handler) {
|
||||
'use strict';
|
||||
if (!type) {
|
||||
throw new Error('Invalid event type');
|
||||
}
|
||||
|
||||
var capture =
|
||||
goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(src);
|
||||
if (!listenerMap) {
|
||||
src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
|
||||
new goog.events.ListenerMap(src);
|
||||
}
|
||||
|
||||
var listenerObj = /** @type {goog.events.Listener} */ (
|
||||
listenerMap.add(type, listener, callOnce, capture, opt_handler));
|
||||
|
||||
// If the listenerObj already has a proxy, it has been set up
|
||||
// previously. We simply return.
|
||||
if (listenerObj.proxy) {
|
||||
return listenerObj;
|
||||
}
|
||||
|
||||
var proxy = goog.events.getProxy();
|
||||
listenerObj.proxy = proxy;
|
||||
|
||||
proxy.src = src;
|
||||
proxy.listener = listenerObj;
|
||||
|
||||
// Attach the proxy through the browser's API
|
||||
if (src.addEventListener) {
|
||||
// Don't pass an object as `capture` if the browser doesn't support that.
|
||||
if (!goog.events.BrowserFeature.PASSIVE_EVENTS) {
|
||||
opt_options = capture;
|
||||
}
|
||||
// Don't break tests that expect a boolean.
|
||||
if (opt_options === undefined) opt_options = false;
|
||||
src.addEventListener(type.toString(), proxy, opt_options);
|
||||
} else if (src.attachEvent) {
|
||||
// The else if above used to be an unconditional else. It would call
|
||||
// attachEvent come gws or high water. This would sometimes throw an
|
||||
// exception on IE11, spoiling the day of some callers. The previous
|
||||
// incarnation of this code, from 2007, indicates that it replaced an
|
||||
// earlier still version that caused excess allocations on IE6.
|
||||
src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
|
||||
} else if (src.addListener && src.removeListener) {
|
||||
// In IE, MediaQueryList uses addListener() insteadd of addEventListener. In
|
||||
// Safari, there is no global for the MediaQueryList constructor, so we just
|
||||
// check whether the object "looks like" MediaQueryList.
|
||||
goog.asserts.assert(
|
||||
type === 'change', 'MediaQueryList only has a change event');
|
||||
src.addListener(proxy);
|
||||
} else {
|
||||
throw new Error('addEventListener and attachEvent are unavailable.');
|
||||
}
|
||||
|
||||
goog.events.listenerCountEstimate_++;
|
||||
return listenerObj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for returning a proxy function.
|
||||
* @return {!Function} A new or reused function object.
|
||||
*/
|
||||
goog.events.getProxy = function() {
|
||||
'use strict';
|
||||
const proxyCallbackFunction = goog.events.handleBrowserEvent_;
|
||||
const f = function(eventObject) {
|
||||
return proxyCallbackFunction.call(f.src, f.listener, eventObject);
|
||||
};
|
||||
return f;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener for a specific event on a native event
|
||||
* target (such as a DOM element) or an object that has implemented
|
||||
* {@link goog.events.Listenable}. After the event has fired the event
|
||||
* listener is removed from the target.
|
||||
*
|
||||
* If an existing listener already exists, listenOnce will do
|
||||
* nothing. In particular, if the listener was previously registered
|
||||
* via listen(), listenOnce() will not turn the listener into a
|
||||
* one-off listener. Similarly, if there is already an existing
|
||||
* one-off listener, listenOnce does not modify the listeners (it is
|
||||
* still a once listener).
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The node to listen
|
||||
* to events on.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type or array of event types.
|
||||
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
|
||||
* listener Callback method.
|
||||
* @param {(boolean|!AddEventListenerOptions)=} opt_options
|
||||
* @param {T=} opt_handler Element in whose scope to call the listener.
|
||||
* @return {goog.events.Key} Unique key for the listener.
|
||||
* @template T,EVENTOBJ
|
||||
*/
|
||||
goog.events.listenOnce = function(
|
||||
src, type, listener, opt_options, opt_handler) {
|
||||
'use strict';
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
goog.events.listenOnce(src, type[i], listener, opt_options, opt_handler);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
listener = goog.events.wrapListener(listener);
|
||||
if (goog.events.Listenable.isImplementedBy(src)) {
|
||||
var capture =
|
||||
goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
|
||||
return src.listenOnce(
|
||||
/** @type {string|!goog.events.EventId} */ (type), listener, capture,
|
||||
opt_handler);
|
||||
} else {
|
||||
return goog.events.listen_(
|
||||
/** @type {!EventTarget} */ (src), type, listener,
|
||||
/* callOnce */ true, opt_options, opt_handler);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener with a specific event wrapper on a DOM Node or an
|
||||
* object that has implemented {@link goog.events.Listenable}. A listener can
|
||||
* only be added once to an object.
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The target to
|
||||
* listen to events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
|
||||
* Callback method, or an object with a handleEvent function.
|
||||
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
|
||||
* false).
|
||||
* @param {T=} opt_handler Element in whose scope to call the listener.
|
||||
* @template T
|
||||
*/
|
||||
goog.events.listenWithWrapper = function(
|
||||
src, wrapper, listener, opt_capt, opt_handler) {
|
||||
'use strict';
|
||||
wrapper.listen(src, listener, opt_capt, opt_handler);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listen().
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The target to stop
|
||||
* listening to events on.
|
||||
* @param {string|Array<string>|
|
||||
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
|
||||
* type Event type or array of event types to unlisten to.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener The
|
||||
* listener function to remove.
|
||||
* @param {(boolean|!EventListenerOptions)=} opt_options
|
||||
* whether the listener is fired during the capture or bubble phase of the
|
||||
* event.
|
||||
* @param {Object=} opt_handler Element in whose scope to call the listener.
|
||||
* @return {?boolean} indicating whether the listener was there to remove.
|
||||
* @template EVENTOBJ
|
||||
*/
|
||||
goog.events.unlisten = function(src, type, listener, opt_options, opt_handler) {
|
||||
'use strict';
|
||||
if (Array.isArray(type)) {
|
||||
for (var i = 0; i < type.length; i++) {
|
||||
goog.events.unlisten(src, type[i], listener, opt_options, opt_handler);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
var capture =
|
||||
goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
|
||||
|
||||
listener = goog.events.wrapListener(listener);
|
||||
if (goog.events.Listenable.isImplementedBy(src)) {
|
||||
return src.unlisten(
|
||||
/** @type {string|!goog.events.EventId} */ (type), listener, capture,
|
||||
opt_handler);
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
// TODO(chrishenry): We should tighten the API to only accept
|
||||
// non-null objects, or add an assertion here.
|
||||
return false;
|
||||
}
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (src));
|
||||
if (listenerMap) {
|
||||
var listenerObj = listenerMap.getListener(
|
||||
/** @type {string|!goog.events.EventId} */ (type), listener, capture,
|
||||
opt_handler);
|
||||
if (listenerObj) {
|
||||
return goog.events.unlistenByKey(listenerObj);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listen() by the key
|
||||
* returned by listen().
|
||||
*
|
||||
* @param {goog.events.Key} key The key returned by listen() for this
|
||||
* event listener.
|
||||
* @return {boolean} indicating whether the listener was there to remove.
|
||||
*/
|
||||
goog.events.unlistenByKey = function(key) {
|
||||
'use strict';
|
||||
// TODO(chrishenry): Remove this check when tests that rely on this
|
||||
// are fixed.
|
||||
if (typeof key === 'number') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var listener = key;
|
||||
if (!listener || listener.removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var src = listener.src;
|
||||
if (goog.events.Listenable.isImplementedBy(src)) {
|
||||
return /** @type {!goog.events.Listenable} */ (src).unlistenByKey(listener);
|
||||
}
|
||||
|
||||
var type = listener.type;
|
||||
var proxy = listener.proxy;
|
||||
if (src.removeEventListener) {
|
||||
src.removeEventListener(type, proxy, listener.capture);
|
||||
} else if (src.detachEvent) {
|
||||
src.detachEvent(goog.events.getOnString_(type), proxy);
|
||||
} else if (src.addListener && src.removeListener) {
|
||||
src.removeListener(proxy);
|
||||
}
|
||||
goog.events.listenerCountEstimate_--;
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (src));
|
||||
// TODO(chrishenry): Try to remove this conditional and execute the
|
||||
// first branch always. This should be safe.
|
||||
if (listenerMap) {
|
||||
listenerMap.removeByKey(listener);
|
||||
if (listenerMap.getTypeCount() == 0) {
|
||||
// Null the src, just because this is simple to do (and useful
|
||||
// for IE <= 7).
|
||||
listenerMap.src = null;
|
||||
// We don't use delete here because IE does not allow delete
|
||||
// on a window object.
|
||||
src[goog.events.LISTENER_MAP_PROP_] = null;
|
||||
}
|
||||
} else {
|
||||
/** @type {!goog.events.Listener} */ (listener).markAsRemoved();
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listenWithWrapper().
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The target to stop
|
||||
* listening to events on.
|
||||
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener The
|
||||
* listener function to remove.
|
||||
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
|
||||
* whether the listener is fired during the capture or bubble phase of the
|
||||
* event.
|
||||
* @param {Object=} opt_handler Element in whose scope to call the listener.
|
||||
*/
|
||||
goog.events.unlistenWithWrapper = function(
|
||||
src, wrapper, listener, opt_capt, opt_handler) {
|
||||
'use strict';
|
||||
wrapper.unlisten(src, listener, opt_capt, opt_handler);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all listeners from an object. You can also optionally
|
||||
* remove listeners of a particular type.
|
||||
*
|
||||
* @param {Object|undefined} obj Object to remove listeners from. Must be an
|
||||
* EventTarget or a goog.events.Listenable.
|
||||
* @param {string|!goog.events.EventId=} opt_type Type of event to remove.
|
||||
* Default is all types.
|
||||
* @return {number} Number of listeners removed.
|
||||
*/
|
||||
goog.events.removeAll = function(obj, opt_type) {
|
||||
'use strict';
|
||||
// TODO(chrishenry): Change the type of obj to
|
||||
// (!EventTarget|!goog.events.Listenable).
|
||||
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (goog.events.Listenable.isImplementedBy(obj)) {
|
||||
return /** @type {?} */ (obj).removeAllListeners(opt_type);
|
||||
}
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (obj));
|
||||
if (!listenerMap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var typeStr = opt_type && opt_type.toString();
|
||||
for (var type in listenerMap.listeners) {
|
||||
if (!typeStr || type == typeStr) {
|
||||
// Clone so that we don't need to worry about unlistenByKey
|
||||
// changing the content of the ListenerMap.
|
||||
var listeners = listenerMap.listeners[type].concat();
|
||||
for (var i = 0; i < listeners.length; ++i) {
|
||||
if (goog.events.unlistenByKey(listeners[i])) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the listeners for a given object, type and capture phase.
|
||||
*
|
||||
* @param {Object} obj Object to get listeners for.
|
||||
* @param {string|!goog.events.EventId} type Event type.
|
||||
* @param {boolean} capture Capture phase?.
|
||||
* @return {!Array<!goog.events.Listener>} Array of listener objects.
|
||||
*/
|
||||
goog.events.getListeners = function(obj, type, capture) {
|
||||
'use strict';
|
||||
if (goog.events.Listenable.isImplementedBy(obj)) {
|
||||
return /** @type {!goog.events.Listenable} */ (obj).getListeners(
|
||||
type, capture);
|
||||
} else {
|
||||
if (!obj) {
|
||||
// TODO(chrishenry): We should tighten the API to accept
|
||||
// !EventTarget|goog.events.Listenable, and add an assertion here.
|
||||
return [];
|
||||
}
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (obj));
|
||||
return listenerMap ? listenerMap.getListeners(type, capture) : [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the goog.events.Listener for the event or null if no such listener is
|
||||
* in use.
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} src The target from
|
||||
* which to get listeners.
|
||||
* @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.
|
||||
* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
|
||||
* listener function to get.
|
||||
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
|
||||
* whether the listener is fired during the
|
||||
* capture or bubble phase of the event.
|
||||
* @param {Object=} opt_handler Element in whose scope to call the listener.
|
||||
* @return {goog.events.ListenableKey} the found listener or null if not found.
|
||||
* @template EVENTOBJ
|
||||
*/
|
||||
goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
|
||||
'use strict';
|
||||
// TODO(chrishenry): Change type from ?string to string, or add assertion.
|
||||
type = /** @type {string} */ (type);
|
||||
listener = goog.events.wrapListener(listener);
|
||||
var capture = !!opt_capt;
|
||||
if (goog.events.Listenable.isImplementedBy(src)) {
|
||||
return src.getListener(type, listener, capture, opt_handler);
|
||||
}
|
||||
|
||||
if (!src) {
|
||||
// TODO(chrishenry): We should tighten the API to only accept
|
||||
// non-null objects, or add an assertion here.
|
||||
return null;
|
||||
}
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (src));
|
||||
if (listenerMap) {
|
||||
return listenerMap.getListener(type, listener, capture, opt_handler);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether an event target has any active listeners matching the
|
||||
* specified signature. If either the type or capture parameters are
|
||||
* unspecified, the function will match on the remaining criteria.
|
||||
*
|
||||
* @param {EventTarget|goog.events.Listenable} obj Target to get
|
||||
* listeners for.
|
||||
* @param {string|!goog.events.EventId=} opt_type Event type.
|
||||
* @param {boolean=} opt_capture Whether to check for capture or bubble-phase
|
||||
* listeners.
|
||||
* @return {boolean} Whether an event target has one or more listeners matching
|
||||
* the requested type and/or capture phase.
|
||||
*/
|
||||
goog.events.hasListener = function(obj, opt_type, opt_capture) {
|
||||
'use strict';
|
||||
if (goog.events.Listenable.isImplementedBy(obj)) {
|
||||
return obj.hasListener(opt_type, opt_capture);
|
||||
}
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {!EventTarget} */ (obj));
|
||||
return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Provides a nice string showing the normalized event objects public members
|
||||
* @param {Object} e Event Object.
|
||||
* @return {string} String of the public members of the normalized event object.
|
||||
*/
|
||||
goog.events.expose = function(e) {
|
||||
'use strict';
|
||||
var str = [];
|
||||
for (var key in e) {
|
||||
if (e[key] && e[key].id) {
|
||||
str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
|
||||
} else {
|
||||
str.push(key + ' = ' + e[key]);
|
||||
}
|
||||
}
|
||||
return str.join('\n');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string with on prepended to the specified type. This is used for IE
|
||||
* which expects "on" to be prepended. This function caches the string in order
|
||||
* to avoid extra allocations in steady state.
|
||||
* @param {string} type Event type.
|
||||
* @return {string} The type string with 'on' prepended.
|
||||
* @private
|
||||
*/
|
||||
goog.events.getOnString_ = function(type) {
|
||||
'use strict';
|
||||
if (type in goog.events.onStringMap_) {
|
||||
return goog.events.onStringMap_[type];
|
||||
}
|
||||
return goog.events.onStringMap_[type] = goog.events.onString_ + type;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires an object's listeners of a particular type and phase
|
||||
*
|
||||
* @param {Object} obj Object whose listeners to call.
|
||||
* @param {string|!goog.events.EventId} type Event type.
|
||||
* @param {boolean} capture Which event phase.
|
||||
* @param {Object} eventObject Event object to be passed to listener.
|
||||
* @return {boolean} True if all listeners returned true else false.
|
||||
*/
|
||||
goog.events.fireListeners = function(obj, type, capture, eventObject) {
|
||||
'use strict';
|
||||
if (goog.events.Listenable.isImplementedBy(obj)) {
|
||||
return /** @type {!goog.events.Listenable} */ (obj).fireListeners(
|
||||
type, capture, eventObject);
|
||||
}
|
||||
|
||||
return goog.events.fireListeners_(obj, type, capture, eventObject);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires an object's listeners of a particular type and phase.
|
||||
* @param {Object} obj Object whose listeners to call.
|
||||
* @param {string|!goog.events.EventId} type Event type.
|
||||
* @param {boolean} capture Which event phase.
|
||||
* @param {Object} eventObject Event object to be passed to listener.
|
||||
* @return {boolean} True if all listeners returned true else false.
|
||||
* @private
|
||||
*/
|
||||
goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
|
||||
'use strict';
|
||||
/** @type {boolean} */
|
||||
var retval = true;
|
||||
|
||||
var listenerMap = goog.events.getListenerMap_(
|
||||
/** @type {EventTarget} */ (obj));
|
||||
if (listenerMap) {
|
||||
// TODO(chrishenry): Original code avoids array creation when there
|
||||
// is no listener, so we do the same. If this optimization turns
|
||||
// out to be not required, we can replace this with
|
||||
// listenerMap.getListeners(type, capture) instead, which is simpler.
|
||||
var listenerArray = listenerMap.listeners[type.toString()];
|
||||
if (listenerArray) {
|
||||
listenerArray = listenerArray.concat();
|
||||
for (var i = 0; i < listenerArray.length; i++) {
|
||||
var listener = listenerArray[i];
|
||||
// We might not have a listener if the listener was removed.
|
||||
if (listener && listener.capture == capture && !listener.removed) {
|
||||
var result = goog.events.fireListener(listener, eventObject);
|
||||
retval = retval && (result !== false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires a listener with a set of arguments
|
||||
*
|
||||
* @param {goog.events.Listener} listener The listener object to call.
|
||||
* @param {Object} eventObject The event object to pass to the listener.
|
||||
* @return {*} Result of listener.
|
||||
*/
|
||||
goog.events.fireListener = function(listener, eventObject) {
|
||||
'use strict';
|
||||
var listenerFn = listener.listener;
|
||||
var listenerHandler = listener.handler || listener.src;
|
||||
|
||||
if (listener.callOnce) {
|
||||
goog.events.unlistenByKey(listener);
|
||||
}
|
||||
return listenerFn.call(listenerHandler, eventObject);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the total number of listeners currently in the system.
|
||||
* @return {number} Number of listeners.
|
||||
* @deprecated This returns estimated count, now that Closure no longer
|
||||
* stores a central listener registry. We still return an estimation
|
||||
* to keep existing listener-related tests passing. In the near future,
|
||||
* this function will be removed.
|
||||
*/
|
||||
goog.events.getTotalListenerCount = function() {
|
||||
'use strict';
|
||||
return goog.events.listenerCountEstimate_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches an event (or event like object) and calls all listeners
|
||||
* listening for events of this type. The type of the event is decided by the
|
||||
* type property on the event object.
|
||||
*
|
||||
* If any of the listeners returns false OR calls preventDefault then this
|
||||
* function will return false. If one of the capture listeners calls
|
||||
* stopPropagation, then the bubble listeners won't fire.
|
||||
*
|
||||
* @param {goog.events.Listenable} src The event target.
|
||||
* @param {goog.events.EventLike} e Event object.
|
||||
* @return {boolean} If anyone called preventDefault on the event object (or
|
||||
* if any of the handlers returns false) this will also return false.
|
||||
* If there are no handlers, or if all handlers return true, this returns
|
||||
* true.
|
||||
*/
|
||||
goog.events.dispatchEvent = function(src, e) {
|
||||
'use strict';
|
||||
goog.asserts.assert(
|
||||
goog.events.Listenable.isImplementedBy(src),
|
||||
'Can not use goog.events.dispatchEvent with ' +
|
||||
'non-goog.events.Listenable instance.');
|
||||
return src.dispatchEvent(e);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for the browser event entry point using the
|
||||
* given error handler.
|
||||
*
|
||||
* @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
|
||||
* protect the entry point.
|
||||
*/
|
||||
goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
|
||||
'use strict';
|
||||
goog.events.handleBrowserEvent_ =
|
||||
errorHandler.protectEntryPoint(goog.events.handleBrowserEvent_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles an event and dispatches it to the correct listeners. This
|
||||
* function is a proxy for the real listener the user specified.
|
||||
*
|
||||
* @param {goog.events.Listener} listener The listener object.
|
||||
* @param {Event=} opt_evt Optional event object that gets passed in via the
|
||||
* native event handlers.
|
||||
* @return {*} Result of the event handler.
|
||||
* @this {EventTarget} The object or Element that fired the event.
|
||||
* @private
|
||||
*/
|
||||
goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
|
||||
'use strict';
|
||||
if (listener.removed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, simply fire the listener.
|
||||
return goog.events.fireListener(
|
||||
listener, new goog.events.BrowserEvent(opt_evt, this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This is used to mark the IE event object so we do not do the Closure pass
|
||||
* twice for a bubbling event.
|
||||
* @param {Event} e The IE browser event.
|
||||
* @private
|
||||
*/
|
||||
goog.events.markIeEvent_ = function(e) {
|
||||
'use strict';
|
||||
// Only the keyCode and the returnValue can be changed. We use keyCode for
|
||||
// non keyboard events.
|
||||
// event.returnValue is a bit more tricky. It is undefined by default. A
|
||||
// boolean false prevents the default action. In a window.onbeforeunload and
|
||||
// the returnValue is non undefined it will be alerted. However, we will only
|
||||
// modify the returnValue for keyboard events. We can get a problem if non
|
||||
// closure events sets the keyCode or the returnValue
|
||||
|
||||
var useReturnValue = false;
|
||||
|
||||
if (e.keyCode == 0) {
|
||||
// We cannot change the keyCode in case that srcElement is input[type=file].
|
||||
// We could test that that is the case but that would allocate 3 objects.
|
||||
// If we use try/catch we will only allocate extra objects in the case of a
|
||||
// failure.
|
||||
|
||||
try {
|
||||
e.keyCode = -1;
|
||||
return;
|
||||
} catch (ex) {
|
||||
useReturnValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (useReturnValue ||
|
||||
/** @type {boolean|undefined} */ (e.returnValue) == undefined) {
|
||||
e.returnValue = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This is used to check if an IE event has already been handled by the Closure
|
||||
* system so we do not do the Closure pass twice for a bubbling event.
|
||||
* @param {Event} e The IE browser event.
|
||||
* @return {boolean} True if the event object has been marked.
|
||||
* @private
|
||||
*/
|
||||
goog.events.isMarkedIeEvent_ = function(e) {
|
||||
'use strict';
|
||||
return e.keyCode < 0 || e.returnValue != undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Counter to create unique event ids.
|
||||
* @private {number}
|
||||
*/
|
||||
goog.events.uniqueIdCounter_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a unique event id.
|
||||
*
|
||||
* @param {string} identifier The identifier.
|
||||
* @return {string} A unique identifier.
|
||||
* @idGenerator {unique}
|
||||
*/
|
||||
goog.events.getUniqueId = function(identifier) {
|
||||
'use strict';
|
||||
return identifier + '_' + goog.events.uniqueIdCounter_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {EventTarget} src The source object.
|
||||
* @return {goog.events.ListenerMap} A listener map for the given
|
||||
* source object, or null if none exists.
|
||||
* @private
|
||||
*/
|
||||
goog.events.getListenerMap_ = function(src) {
|
||||
'use strict';
|
||||
var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
|
||||
// IE serializes the property as well (e.g. when serializing outer
|
||||
// HTML). So we must check that the value is of the correct type.
|
||||
return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Expando property for listener function wrapper for Object with
|
||||
* handleEvent.
|
||||
* @private @const {string}
|
||||
*/
|
||||
goog.events.LISTENER_WRAPPER_PROP_ =
|
||||
'__closure_events_fn_' + ((Math.random() * 1e9) >>> 0);
|
||||
|
||||
|
||||
/**
|
||||
* @param {Object|Function} listener The listener function or an
|
||||
* object that contains handleEvent method.
|
||||
* @return {!Function} Either the original function or a function that
|
||||
* calls obj.handleEvent. If the same listener is passed to this
|
||||
* function more than once, the same function is guaranteed to be
|
||||
* returned.
|
||||
*/
|
||||
goog.events.wrapListener = function(listener) {
|
||||
'use strict';
|
||||
goog.asserts.assert(listener, 'Listener can not be null.');
|
||||
|
||||
if (typeof listener === 'function') {
|
||||
return listener;
|
||||
}
|
||||
|
||||
goog.asserts.assert(
|
||||
listener.handleEvent, 'An object listener must have handleEvent method.');
|
||||
if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
|
||||
listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) {
|
||||
'use strict';
|
||||
return /** @type {?} */ (listener).handleEvent(e);
|
||||
};
|
||||
}
|
||||
return listener[goog.events.LISTENER_WRAPPER_PROP_];
|
||||
};
|
||||
|
||||
|
||||
// Register the browser event handler as an entry point, so that
|
||||
// it can be monitored for exception handling, etc.
|
||||
goog.debug.entryPointRegistry.register(
|
||||
/**
|
||||
* @param {function(!Function): !Function} transformer The transforming
|
||||
* function.
|
||||
*/
|
||||
function(transformer) {
|
||||
'use strict';
|
||||
goog.events.handleBrowserEvent_ =
|
||||
transformer(goog.events.handleBrowserEvent_);
|
||||
});
|
||||
Executable
+495
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A disposable implementation of a custom
|
||||
* listenable/event target. See also: documentation for
|
||||
* `goog.events.Listenable`.
|
||||
*
|
||||
* @see ../demos/eventtarget.html
|
||||
* @see goog.events.Listenable
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.EventTarget');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.Listenable');
|
||||
goog.require('goog.events.ListenerMap');
|
||||
goog.require('goog.object');
|
||||
goog.requireType('goog.events.EventId');
|
||||
goog.requireType('goog.events.EventLike');
|
||||
goog.requireType('goog.events.ListenableKey');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of `goog.events.Listenable` with full W3C
|
||||
* EventTarget-like support (capture/bubble mechanism, stopping event
|
||||
* propagation, preventing default actions).
|
||||
*
|
||||
* You may subclass this class to turn your class into a Listenable.
|
||||
*
|
||||
* Unless propagation is stopped, an event dispatched by an
|
||||
* EventTarget will bubble to the parent returned by
|
||||
* `getParentEventTarget`. To set the parent, call
|
||||
* `setParentEventTarget`. Subclasses that don't support
|
||||
* changing the parent can override the setter to throw an error.
|
||||
*
|
||||
* Example usage:
|
||||
* <pre>
|
||||
* var source = new goog.events.EventTarget();
|
||||
* function handleEvent(e) {
|
||||
* alert('Type: ' + e.type + '; Target: ' + e.target);
|
||||
* }
|
||||
* source.listen('foo', handleEvent);
|
||||
* // Or: goog.events.listen(source, 'foo', handleEvent);
|
||||
* ...
|
||||
* source.dispatchEvent('foo'); // will call handleEvent
|
||||
* ...
|
||||
* source.unlisten('foo', handleEvent);
|
||||
* // Or: goog.events.unlisten(source, 'foo', handleEvent);
|
||||
* </pre>
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @implements {goog.events.Listenable}
|
||||
*/
|
||||
goog.events.EventTarget = function() {
|
||||
'use strict';
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Maps of event type to an array of listeners.
|
||||
* @private {!goog.events.ListenerMap}
|
||||
*/
|
||||
this.eventTargetListeners_ = new goog.events.ListenerMap(this);
|
||||
|
||||
/**
|
||||
* The object to use for event.target. Useful when mixing in an
|
||||
* EventTarget to another object.
|
||||
* @private {!Object}
|
||||
*/
|
||||
this.actualEventTarget_ = this;
|
||||
|
||||
/**
|
||||
* Parent event target, used during event bubbling.
|
||||
*
|
||||
* TODO(chrishenry): Change this to goog.events.Listenable. This
|
||||
* currently breaks people who expect getParentEventTarget to return
|
||||
* goog.events.EventTarget.
|
||||
*
|
||||
* @private {?goog.events.EventTarget}
|
||||
*/
|
||||
this.parentEventTarget_ = null;
|
||||
};
|
||||
goog.inherits(goog.events.EventTarget, goog.Disposable);
|
||||
goog.events.Listenable.addImplementation(goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* An artificial cap on the number of ancestors you can have. This is mainly
|
||||
* for loop detection.
|
||||
* @const {number}
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parent of this event target to use for bubbling.
|
||||
*
|
||||
* @return {goog.events.EventTarget} The parent EventTarget or null if
|
||||
* there is no parent.
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.getParentEventTarget = function() {
|
||||
'use strict';
|
||||
return this.parentEventTarget_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the parent of this event target to use for capture/bubble
|
||||
* mechanism.
|
||||
* @param {goog.events.EventTarget} parent Parent listenable (null if none).
|
||||
*/
|
||||
goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
|
||||
'use strict';
|
||||
this.parentEventTarget_ = parent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener to the event target. The same handler can only be
|
||||
* added once per the type. Even if you add the same handler multiple times
|
||||
* using the same type then it will only be called once when the event is
|
||||
* dispatched.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type The type of the event to listen for
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
|
||||
* to handle the event. The handler can also be an object that implements
|
||||
* the handleEvent method which takes the event object as argument.
|
||||
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
|
||||
* whether the listener is fired during the capture or bubble phase
|
||||
* of the event.
|
||||
* @param {Object=} opt_handlerScope Object in whose scope to call
|
||||
* the listener.
|
||||
* @deprecated Use `#listen` instead, when possible. Otherwise, use
|
||||
* `goog.events.listen` if you are passing Object
|
||||
* (instead of Function) as handler.
|
||||
*/
|
||||
goog.events.EventTarget.prototype.addEventListener = function(
|
||||
type, handler, opt_capture, opt_handlerScope) {
|
||||
'use strict';
|
||||
goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener from the event target. The handler must be the
|
||||
* same object as the one added. If the handler has not been added then
|
||||
* nothing is done.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type The type of the event to listen for
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
|
||||
* to handle the event. The handler can also be an object that implements
|
||||
* the handleEvent method which takes the event object as argument.
|
||||
* @param {boolean=} opt_capture In DOM-compliant browsers, this determines
|
||||
* whether the listener is fired during the capture or bubble phase
|
||||
* of the event.
|
||||
* @param {Object=} opt_handlerScope Object in whose scope to call
|
||||
* the listener.
|
||||
* @deprecated Use `#unlisten` instead, when possible. Otherwise, use
|
||||
* `goog.events.unlisten` if you are passing Object
|
||||
* (instead of Function) as handler.
|
||||
*/
|
||||
goog.events.EventTarget.prototype.removeEventListener = function(
|
||||
type, handler, opt_capture, opt_handlerScope) {
|
||||
'use strict';
|
||||
goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?goog.events.EventLike} e Event object.
|
||||
* @return {boolean} If anyone called preventDefault on the event object (or
|
||||
* if any of the listeners returns false) this will also return false.
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.dispatchEvent = function(e) {
|
||||
'use strict';
|
||||
this.assertInitialized_();
|
||||
|
||||
var ancestorsTree, ancestor = this.getParentEventTarget();
|
||||
if (ancestor) {
|
||||
ancestorsTree = [];
|
||||
var ancestorCount = 1;
|
||||
for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
|
||||
ancestorsTree.push(ancestor);
|
||||
goog.asserts.assert(
|
||||
(++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
|
||||
'infinite loop');
|
||||
}
|
||||
}
|
||||
|
||||
return goog.events.EventTarget.dispatchEventInternal_(
|
||||
this.actualEventTarget_, e, ancestorsTree);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes listeners from this object. Classes that extend EventTarget may
|
||||
* need to override this method in order to remove references to DOM Elements
|
||||
* and additional listeners.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.events.EventTarget.prototype.disposeInternal = function() {
|
||||
'use strict';
|
||||
goog.events.EventTarget.superClass_.disposeInternal.call(this);
|
||||
|
||||
this.removeAllListeners();
|
||||
this.parentEventTarget_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.listen = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
this.assertInitialized_();
|
||||
return this.eventTargetListeners_.add(
|
||||
String(type), listener, false /* callOnce */, opt_useCapture,
|
||||
opt_listenerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.listenOnce = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
return this.eventTargetListeners_.add(
|
||||
String(type), listener, true /* callOnce */, opt_useCapture,
|
||||
opt_listenerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call
|
||||
* the listener.
|
||||
* @return {boolean} Whether any listener was removed.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.unlisten = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
return this.eventTargetListeners_.remove(
|
||||
String(type), listener, opt_useCapture, opt_listenerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!goog.events.ListenableKey} key The key returned by
|
||||
* listen() or listenOnce().
|
||||
* @return {boolean} Whether any listener was removed.
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.unlistenByKey = function(key) {
|
||||
'use strict';
|
||||
return this.eventTargetListeners_.removeByKey(key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId=} opt_type Type of event to remove,
|
||||
* default is to remove all types.
|
||||
* @return {number} Number of listeners removed.
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
|
||||
'use strict';
|
||||
// TODO(chrishenry): Previously, removeAllListeners can be called on
|
||||
// uninitialized EventTarget, so we preserve that behavior. We
|
||||
// should remove this when usages that rely on that fact are purged.
|
||||
if (!this.eventTargetListeners_) {
|
||||
return 0;
|
||||
}
|
||||
return this.eventTargetListeners_.removeAll(opt_type);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
|
||||
* listeners to fire.
|
||||
* @param {boolean} capture The capture mode of the listeners to fire.
|
||||
* @param {EVENTOBJ} eventObject The event object to fire.
|
||||
* @return {boolean} Whether all listeners succeeded without
|
||||
* attempting to prevent default behavior. If any listener returns
|
||||
* false or called goog.events.Event#preventDefault, this returns
|
||||
* false.
|
||||
* @template EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.fireListeners = function(
|
||||
type, capture, eventObject) {
|
||||
'use strict';
|
||||
// TODO(chrishenry): Original code avoids array creation when there
|
||||
// is no listener, so we do the same. If this optimization turns
|
||||
// out to be not required, we can replace this with
|
||||
// getListeners(type, capture) instead, which is simpler.
|
||||
var listenerArray = this.eventTargetListeners_.listeners[String(type)];
|
||||
if (!listenerArray) {
|
||||
return true;
|
||||
}
|
||||
listenerArray = listenerArray.concat();
|
||||
|
||||
var rv = true;
|
||||
for (var i = 0; i < listenerArray.length; ++i) {
|
||||
var listener = listenerArray[i];
|
||||
// We might not have a listener if the listener was removed.
|
||||
if (listener && !listener.removed && listener.capture == capture) {
|
||||
var listenerFn = listener.listener;
|
||||
var listenerHandler = listener.handler || listener.src;
|
||||
|
||||
if (listener.callOnce) {
|
||||
this.unlistenByKey(listener);
|
||||
}
|
||||
rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
|
||||
}
|
||||
}
|
||||
|
||||
return rv && !eventObject.defaultPrevented;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId} type The type of the listeners to fire.
|
||||
* @param {boolean} capture The capture mode of the listeners to fire.
|
||||
* @return {!Array<!goog.events.ListenableKey>} An array of registered
|
||||
* listeners.
|
||||
* @template EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.getListeners = function(type, capture) {
|
||||
'use strict';
|
||||
return this.eventTargetListeners_.getListeners(String(type), capture);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
|
||||
* without the 'on' prefix.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
|
||||
* listener function to get.
|
||||
* @param {boolean} capture Whether the listener is a capturing listener.
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {?goog.events.ListenableKey} the found listener or null if not found.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.getListener = function(
|
||||
type, listener, capture, opt_listenerScope) {
|
||||
'use strict';
|
||||
return this.eventTargetListeners_.getListener(
|
||||
String(type), listener, capture, opt_listenerScope);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
|
||||
* @param {boolean=} opt_capture Whether to check for capture or bubble
|
||||
* listeners.
|
||||
* @return {boolean} Whether there is any active listeners matching
|
||||
* the requested type and/or capture phase.
|
||||
* @template EVENTOBJ
|
||||
* @override
|
||||
*/
|
||||
goog.events.EventTarget.prototype.hasListener = function(
|
||||
opt_type, opt_capture) {
|
||||
'use strict';
|
||||
var id = (opt_type !== undefined) ? String(opt_type) : undefined;
|
||||
return this.eventTargetListeners_.hasListener(id, opt_capture);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the target to be used for `event.target` when firing
|
||||
* event. Mainly used for testing. For example, see
|
||||
* `goog.testing.events.mixinListenable`.
|
||||
* @param {!Object} target The target.
|
||||
*/
|
||||
goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
|
||||
'use strict';
|
||||
this.actualEventTarget_ = target;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the event target instance is initialized properly.
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventTarget.prototype.assertInitialized_ = function() {
|
||||
'use strict';
|
||||
goog.asserts.assert(
|
||||
this.eventTargetListeners_,
|
||||
'Event target is not initialized. Did you call the superclass ' +
|
||||
'(goog.events.EventTarget) constructor?');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches the given event on the ancestorsTree.
|
||||
*
|
||||
* @param {!Object} target The target to dispatch on.
|
||||
* @param {goog.events.Event|Object|string} e The event object.
|
||||
* @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
|
||||
* tree of the target, in reverse order from the closest ancestor
|
||||
* to the root event target. May be null if the target has no ancestor.
|
||||
* @return {boolean} If anyone called preventDefault on the event object (or
|
||||
* if any of the listeners returns false) this will also return false.
|
||||
* @private
|
||||
*/
|
||||
goog.events.EventTarget.dispatchEventInternal_ = function(
|
||||
target, e, opt_ancestorsTree) {
|
||||
'use strict';
|
||||
/** @suppress {missingProperties} */
|
||||
var type = e.type || /** @type {string} */ (e);
|
||||
|
||||
// If accepting a string or object, create a custom event object so that
|
||||
// preventDefault and stopPropagation work with the event.
|
||||
if (typeof e === 'string') {
|
||||
e = new goog.events.Event(e, target);
|
||||
} else if (!(e instanceof goog.events.Event)) {
|
||||
var oldEvent = e;
|
||||
e = new goog.events.Event(type, target);
|
||||
goog.object.extend(e, oldEvent);
|
||||
} else {
|
||||
e.target = e.target || target;
|
||||
}
|
||||
|
||||
var rv = true, currentTarget;
|
||||
|
||||
// Executes all capture listeners on the ancestors, if any.
|
||||
if (opt_ancestorsTree) {
|
||||
for (var i = opt_ancestorsTree.length - 1;
|
||||
!e.hasPropagationStopped() && i >= 0; i--) {
|
||||
currentTarget = e.currentTarget = opt_ancestorsTree[i];
|
||||
rv = currentTarget.fireListeners(type, true, e) && rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Executes capture and bubble listeners on the target.
|
||||
if (!e.hasPropagationStopped()) {
|
||||
currentTarget = /** @type {?} */ (e.currentTarget = target);
|
||||
rv = currentTarget.fireListeners(type, true, e) && rv;
|
||||
if (!e.hasPropagationStopped()) {
|
||||
rv = currentTarget.fireListeners(type, false, e) && rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Executes all bubble listeners on the ancestors, if any.
|
||||
if (opt_ancestorsTree) {
|
||||
for (i = 0; !e.hasPropagationStopped() && i < opt_ancestorsTree.length;
|
||||
i++) {
|
||||
currentTarget = e.currentTarget = opt_ancestorsTree[i];
|
||||
rv = currentTarget.fireListeners(type, false, e) && rv;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
};
|
||||
Executable
+437
@@ -0,0 +1,437 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Event Types.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.events.EventType');
|
||||
goog.provide('goog.events.MouseAsMouseEventType');
|
||||
goog.provide('goog.events.MouseEvents');
|
||||
goog.provide('goog.events.PointerAsMouseEventType');
|
||||
goog.provide('goog.events.PointerAsTouchEventType');
|
||||
goog.provide('goog.events.PointerFallbackEventType');
|
||||
goog.provide('goog.events.PointerTouchFallbackEventType');
|
||||
|
||||
goog.require('goog.events.BrowserFeature');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Returns a prefixed event name for the current browser.
|
||||
* @param {string} eventName The name of the event.
|
||||
* @return {string} The prefixed event name.
|
||||
* @private
|
||||
*/
|
||||
goog.events.getVendorPrefixedName_ = function(eventName) {
|
||||
'use strict';
|
||||
return goog.userAgent.WEBKIT ? 'webkit' + eventName : eventName.toLowerCase();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constants for event names.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.events.EventType = {
|
||||
// Mouse events
|
||||
CLICK: 'click',
|
||||
RIGHTCLICK: 'rightclick',
|
||||
DBLCLICK: 'dblclick',
|
||||
AUXCLICK: 'auxclick',
|
||||
MOUSEDOWN: 'mousedown',
|
||||
MOUSEUP: 'mouseup',
|
||||
MOUSEOVER: 'mouseover',
|
||||
MOUSEOUT: 'mouseout',
|
||||
MOUSEMOVE: 'mousemove',
|
||||
MOUSEENTER: 'mouseenter',
|
||||
MOUSELEAVE: 'mouseleave',
|
||||
|
||||
// Non-existent event; will never fire. This exists as a mouse counterpart to
|
||||
// POINTERCANCEL.
|
||||
MOUSECANCEL: 'mousecancel',
|
||||
|
||||
// Selection events.
|
||||
// https://www.w3.org/TR/selection-api/
|
||||
SELECTIONCHANGE: 'selectionchange',
|
||||
SELECTSTART: 'selectstart', // IE, Safari, Chrome
|
||||
|
||||
// Wheel events
|
||||
// http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
|
||||
WHEEL: 'wheel',
|
||||
|
||||
// Key events
|
||||
KEYPRESS: 'keypress',
|
||||
KEYDOWN: 'keydown',
|
||||
KEYUP: 'keyup',
|
||||
|
||||
// Focus
|
||||
BLUR: 'blur',
|
||||
FOCUS: 'focus',
|
||||
DEACTIVATE: 'deactivate', // IE only
|
||||
FOCUSIN: 'focusin',
|
||||
FOCUSOUT: 'focusout',
|
||||
|
||||
// Forms
|
||||
CHANGE: 'change',
|
||||
RESET: 'reset',
|
||||
SELECT: 'select',
|
||||
SUBMIT: 'submit',
|
||||
INPUT: 'input',
|
||||
PROPERTYCHANGE: 'propertychange', // IE only
|
||||
|
||||
// Drag and drop
|
||||
DRAGSTART: 'dragstart',
|
||||
DRAG: 'drag',
|
||||
DRAGENTER: 'dragenter',
|
||||
DRAGOVER: 'dragover',
|
||||
DRAGLEAVE: 'dragleave',
|
||||
DROP: 'drop',
|
||||
DRAGEND: 'dragend',
|
||||
|
||||
// Touch events
|
||||
// Note that other touch events exist, but we should follow the W3C list here.
|
||||
// http://www.w3.org/TR/touch-events/#list-of-touchevent-types
|
||||
TOUCHSTART: 'touchstart',
|
||||
TOUCHMOVE: 'touchmove',
|
||||
TOUCHEND: 'touchend',
|
||||
TOUCHCANCEL: 'touchcancel',
|
||||
|
||||
// Misc
|
||||
BEFOREUNLOAD: 'beforeunload',
|
||||
CONSOLEMESSAGE: 'consolemessage',
|
||||
CONTEXTMENU: 'contextmenu',
|
||||
DEVICECHANGE: 'devicechange',
|
||||
DEVICEMOTION: 'devicemotion',
|
||||
DEVICEORIENTATION: 'deviceorientation',
|
||||
DOMCONTENTLOADED: 'DOMContentLoaded',
|
||||
ERROR: 'error',
|
||||
HELP: 'help',
|
||||
LOAD: 'load',
|
||||
LOSECAPTURE: 'losecapture',
|
||||
ORIENTATIONCHANGE: 'orientationchange',
|
||||
READYSTATECHANGE: 'readystatechange',
|
||||
RESIZE: 'resize',
|
||||
SCROLL: 'scroll',
|
||||
UNLOAD: 'unload',
|
||||
|
||||
// Media events
|
||||
CANPLAY: 'canplay',
|
||||
CANPLAYTHROUGH: 'canplaythrough',
|
||||
DURATIONCHANGE: 'durationchange',
|
||||
EMPTIED: 'emptied',
|
||||
ENDED: 'ended',
|
||||
LOADEDDATA: 'loadeddata',
|
||||
LOADEDMETADATA: 'loadedmetadata',
|
||||
PAUSE: 'pause',
|
||||
PLAY: 'play',
|
||||
PLAYING: 'playing',
|
||||
PROGRESS: 'progress',
|
||||
RATECHANGE: 'ratechange',
|
||||
SEEKED: 'seeked',
|
||||
SEEKING: 'seeking',
|
||||
STALLED: 'stalled',
|
||||
SUSPEND: 'suspend',
|
||||
TIMEUPDATE: 'timeupdate',
|
||||
VOLUMECHANGE: 'volumechange',
|
||||
WAITING: 'waiting',
|
||||
|
||||
// Media Source Extensions events
|
||||
// https://www.w3.org/TR/media-source/#mediasource-events
|
||||
SOURCEOPEN: 'sourceopen',
|
||||
SOURCEENDED: 'sourceended',
|
||||
SOURCECLOSED: 'sourceclosed',
|
||||
// https://www.w3.org/TR/media-source/#sourcebuffer-events
|
||||
ABORT: 'abort',
|
||||
UPDATE: 'update',
|
||||
UPDATESTART: 'updatestart',
|
||||
UPDATEEND: 'updateend',
|
||||
|
||||
// HTML 5 History events
|
||||
// See http://www.w3.org/TR/html5/browsers.html#event-definitions-0
|
||||
HASHCHANGE: 'hashchange',
|
||||
PAGEHIDE: 'pagehide',
|
||||
PAGESHOW: 'pageshow',
|
||||
POPSTATE: 'popstate',
|
||||
|
||||
// Copy and Paste
|
||||
// Support is limited. Make sure it works on your favorite browser
|
||||
// before using.
|
||||
// http://www.quirksmode.org/dom/events/cutcopypaste.html
|
||||
COPY: 'copy',
|
||||
PASTE: 'paste',
|
||||
CUT: 'cut',
|
||||
BEFORECOPY: 'beforecopy',
|
||||
BEFORECUT: 'beforecut',
|
||||
BEFOREPASTE: 'beforepaste',
|
||||
|
||||
// HTML5 online/offline events.
|
||||
// http://www.w3.org/TR/offline-webapps/#related
|
||||
ONLINE: 'online',
|
||||
OFFLINE: 'offline',
|
||||
|
||||
// HTML 5 worker events
|
||||
MESSAGE: 'message',
|
||||
CONNECT: 'connect',
|
||||
|
||||
// Service Worker Events - ServiceWorkerGlobalScope context
|
||||
// See https://w3c.github.io/ServiceWorker/#execution-context-events
|
||||
// Note: message event defined in worker events section
|
||||
INSTALL: 'install',
|
||||
ACTIVATE: 'activate',
|
||||
FETCH: 'fetch',
|
||||
FOREIGNFETCH: 'foreignfetch',
|
||||
MESSAGEERROR: 'messageerror',
|
||||
|
||||
// Service Worker Events - Document context
|
||||
// See https://w3c.github.io/ServiceWorker/#document-context-events
|
||||
STATECHANGE: 'statechange',
|
||||
UPDATEFOUND: 'updatefound',
|
||||
CONTROLLERCHANGE: 'controllerchange',
|
||||
|
||||
// CSS animation events.
|
||||
ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
|
||||
ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
|
||||
ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'),
|
||||
|
||||
// CSS transition events. Based on the browser support described at:
|
||||
// https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
|
||||
TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'),
|
||||
|
||||
// W3C Pointer Events
|
||||
// http://www.w3.org/TR/pointerevents/
|
||||
POINTERDOWN: 'pointerdown',
|
||||
POINTERUP: 'pointerup',
|
||||
POINTERCANCEL: 'pointercancel',
|
||||
POINTERMOVE: 'pointermove',
|
||||
POINTEROVER: 'pointerover',
|
||||
POINTEROUT: 'pointerout',
|
||||
POINTERENTER: 'pointerenter',
|
||||
POINTERLEAVE: 'pointerleave',
|
||||
GOTPOINTERCAPTURE: 'gotpointercapture',
|
||||
LOSTPOINTERCAPTURE: 'lostpointercapture',
|
||||
|
||||
// IE specific events.
|
||||
// See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
|
||||
// Note: these events will be supplanted in IE11.
|
||||
MSGESTURECHANGE: 'MSGestureChange',
|
||||
MSGESTUREEND: 'MSGestureEnd',
|
||||
MSGESTUREHOLD: 'MSGestureHold',
|
||||
MSGESTURESTART: 'MSGestureStart',
|
||||
MSGESTURETAP: 'MSGestureTap',
|
||||
MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
|
||||
MSINERTIASTART: 'MSInertiaStart',
|
||||
MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
|
||||
MSPOINTERCANCEL: 'MSPointerCancel',
|
||||
MSPOINTERDOWN: 'MSPointerDown',
|
||||
MSPOINTERENTER: 'MSPointerEnter',
|
||||
MSPOINTERHOVER: 'MSPointerHover',
|
||||
MSPOINTERLEAVE: 'MSPointerLeave',
|
||||
MSPOINTERMOVE: 'MSPointerMove',
|
||||
MSPOINTEROUT: 'MSPointerOut',
|
||||
MSPOINTEROVER: 'MSPointerOver',
|
||||
MSPOINTERUP: 'MSPointerUp',
|
||||
|
||||
// Native IMEs/input tools events.
|
||||
TEXT: 'text',
|
||||
// The textInput event is supported in IE9+, but only in lower case. All other
|
||||
// browsers use the camel-case event name.
|
||||
TEXTINPUT: goog.userAgent.IE ? 'textinput' : 'textInput',
|
||||
COMPOSITIONSTART: 'compositionstart',
|
||||
COMPOSITIONUPDATE: 'compositionupdate',
|
||||
COMPOSITIONEND: 'compositionend',
|
||||
|
||||
// The beforeinput event is initially only supported in Safari. See
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=342670 for Chrome
|
||||
// implementation tracking.
|
||||
BEFOREINPUT: 'beforeinput',
|
||||
|
||||
// Webview tag events
|
||||
// See https://developer.chrome.com/apps/tags/webview
|
||||
EXIT: 'exit',
|
||||
LOADABORT: 'loadabort',
|
||||
LOADCOMMIT: 'loadcommit',
|
||||
LOADREDIRECT: 'loadredirect',
|
||||
LOADSTART: 'loadstart',
|
||||
LOADSTOP: 'loadstop',
|
||||
RESPONSIVE: 'responsive',
|
||||
SIZECHANGED: 'sizechanged',
|
||||
UNRESPONSIVE: 'unresponsive',
|
||||
|
||||
// HTML5 Page Visibility API. See details at
|
||||
// `goog.labs.dom.PageVisibilityMonitor`.
|
||||
VISIBILITYCHANGE: 'visibilitychange',
|
||||
|
||||
// LocalStorage event.
|
||||
STORAGE: 'storage',
|
||||
|
||||
// DOM Level 2 mutation events (deprecated).
|
||||
DOMSUBTREEMODIFIED: 'DOMSubtreeModified',
|
||||
DOMNODEINSERTED: 'DOMNodeInserted',
|
||||
DOMNODEREMOVED: 'DOMNodeRemoved',
|
||||
DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument',
|
||||
DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument',
|
||||
DOMATTRMODIFIED: 'DOMAttrModified',
|
||||
DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified',
|
||||
|
||||
// Print events.
|
||||
BEFOREPRINT: 'beforeprint',
|
||||
AFTERPRINT: 'afterprint',
|
||||
|
||||
// Web app manifest events.
|
||||
BEFOREINSTALLPROMPT: 'beforeinstallprompt',
|
||||
APPINSTALLED: 'appinstalled'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns one of the given pointer fallback event names in order of preference:
|
||||
* 1. pointerEventName
|
||||
* 2. msPointerEventName
|
||||
* 3. fallbackEventName
|
||||
* @param {string} pointerEventName
|
||||
* @param {string} msPointerEventName
|
||||
* @param {string} fallbackEventName
|
||||
* @return {string} The supported pointer or fallback (mouse or touch) event
|
||||
* name.
|
||||
* @private
|
||||
*/
|
||||
goog.events.getPointerFallbackEventName_ = function(
|
||||
pointerEventName, msPointerEventName, fallbackEventName) {
|
||||
'use strict';
|
||||
if (goog.events.BrowserFeature.POINTER_EVENTS) {
|
||||
return pointerEventName;
|
||||
}
|
||||
if (goog.events.BrowserFeature.MSPOINTER_EVENTS) {
|
||||
return msPointerEventName;
|
||||
}
|
||||
return fallbackEventName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constants for pointer event names that fall back to corresponding mouse event
|
||||
* names on unsupported platforms. These are intended to be drop-in replacements
|
||||
* for corresponding values in `goog.events.EventType`.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.events.PointerFallbackEventType = {
|
||||
POINTERDOWN: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERDOWN, goog.events.EventType.MSPOINTERDOWN,
|
||||
goog.events.EventType.MOUSEDOWN),
|
||||
POINTERUP: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERUP, goog.events.EventType.MSPOINTERUP,
|
||||
goog.events.EventType.MOUSEUP),
|
||||
POINTERCANCEL: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERCANCEL,
|
||||
goog.events.EventType.MSPOINTERCANCEL,
|
||||
// When falling back to mouse events, there is no MOUSECANCEL equivalent
|
||||
// of POINTERCANCEL. In this case POINTERUP already falls back to MOUSEUP
|
||||
// which represents both UP and CANCEL. POINTERCANCEL does not fall back
|
||||
// to MOUSEUP to prevent listening twice on the same event.
|
||||
goog.events.EventType.MOUSECANCEL),
|
||||
POINTERMOVE: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERMOVE, goog.events.EventType.MSPOINTERMOVE,
|
||||
goog.events.EventType.MOUSEMOVE),
|
||||
POINTEROVER: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTEROVER, goog.events.EventType.MSPOINTEROVER,
|
||||
goog.events.EventType.MOUSEOVER),
|
||||
POINTEROUT: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTEROUT, goog.events.EventType.MSPOINTEROUT,
|
||||
goog.events.EventType.MOUSEOUT),
|
||||
POINTERENTER: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERENTER, goog.events.EventType.MSPOINTERENTER,
|
||||
goog.events.EventType.MOUSEENTER),
|
||||
POINTERLEAVE: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERLEAVE, goog.events.EventType.MSPOINTERLEAVE,
|
||||
goog.events.EventType.MOUSELEAVE)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constants for pointer event names that fall back to corresponding touch event
|
||||
* names on unsupported platforms. These are intended to be drop-in replacements
|
||||
* for corresponding values in `goog.events.EventType`.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.events.PointerTouchFallbackEventType = {
|
||||
POINTERDOWN: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERDOWN, goog.events.EventType.MSPOINTERDOWN,
|
||||
goog.events.EventType.TOUCHSTART),
|
||||
POINTERUP: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERUP, goog.events.EventType.MSPOINTERUP,
|
||||
goog.events.EventType.TOUCHEND),
|
||||
POINTERCANCEL: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERCANCEL,
|
||||
goog.events.EventType.MSPOINTERCANCEL, goog.events.EventType.TOUCHCANCEL),
|
||||
POINTERMOVE: goog.events.getPointerFallbackEventName_(
|
||||
goog.events.EventType.POINTERMOVE, goog.events.EventType.MSPOINTERMOVE,
|
||||
goog.events.EventType.TOUCHMOVE)
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Mapping of mouse event names to underlying browser event names.
|
||||
* @typedef {{
|
||||
* MOUSEDOWN: string,
|
||||
* MOUSEUP: string,
|
||||
* MOUSECANCEL:string,
|
||||
* MOUSEMOVE:string,
|
||||
* MOUSEOVER:string,
|
||||
* MOUSEOUT:string,
|
||||
* MOUSEENTER:string,
|
||||
* MOUSELEAVE: string,
|
||||
* }}
|
||||
*/
|
||||
goog.events.MouseEvents;
|
||||
|
||||
|
||||
/**
|
||||
* An alias for `goog.events.EventType.MOUSE*` event types that is overridden by
|
||||
* corresponding `POINTER*` event types.
|
||||
* @const {!goog.events.MouseEvents}
|
||||
*/
|
||||
goog.events.PointerAsMouseEventType = {
|
||||
MOUSEDOWN: goog.events.PointerFallbackEventType.POINTERDOWN,
|
||||
MOUSEUP: goog.events.PointerFallbackEventType.POINTERUP,
|
||||
MOUSECANCEL: goog.events.PointerFallbackEventType.POINTERCANCEL,
|
||||
MOUSEMOVE: goog.events.PointerFallbackEventType.POINTERMOVE,
|
||||
MOUSEOVER: goog.events.PointerFallbackEventType.POINTEROVER,
|
||||
MOUSEOUT: goog.events.PointerFallbackEventType.POINTEROUT,
|
||||
MOUSEENTER: goog.events.PointerFallbackEventType.POINTERENTER,
|
||||
MOUSELEAVE: goog.events.PointerFallbackEventType.POINTERLEAVE
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An alias for `goog.events.EventType.MOUSE*` event types that continue to use
|
||||
* mouse events.
|
||||
* @const {!goog.events.MouseEvents}
|
||||
*/
|
||||
goog.events.MouseAsMouseEventType = {
|
||||
MOUSEDOWN: goog.events.EventType.MOUSEDOWN,
|
||||
MOUSEUP: goog.events.EventType.MOUSEUP,
|
||||
MOUSECANCEL: goog.events.EventType.MOUSECANCEL,
|
||||
MOUSEMOVE: goog.events.EventType.MOUSEMOVE,
|
||||
MOUSEOVER: goog.events.EventType.MOUSEOVER,
|
||||
MOUSEOUT: goog.events.EventType.MOUSEOUT,
|
||||
MOUSEENTER: goog.events.EventType.MOUSEENTER,
|
||||
MOUSELEAVE: goog.events.EventType.MOUSELEAVE
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An alias for `goog.events.EventType.TOUCH*` event types that is overridden by
|
||||
* corresponding `POINTER*` event types.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.events.PointerAsTouchEventType = {
|
||||
TOUCHCANCEL: goog.events.PointerTouchFallbackEventType.POINTERCANCEL,
|
||||
TOUCHEND: goog.events.PointerTouchFallbackEventType.POINTERUP,
|
||||
TOUCHMOVE: goog.events.PointerTouchFallbackEventType.POINTERMOVE,
|
||||
TOUCHSTART: goog.events.PointerTouchFallbackEventType.POINTERDOWN
|
||||
};
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the goog.events.EventWrapper interface.
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.EventWrapper');
|
||||
|
||||
goog.requireType('goog.events.EventHandler');
|
||||
goog.requireType('goog.events.ListenableType');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for event wrappers.
|
||||
* @interface
|
||||
*/
|
||||
goog.events.EventWrapper = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener using the wrapper on a DOM Node or an object that has
|
||||
* implemented {@link goog.events.EventTarget}. A listener can only be added
|
||||
* once to an object.
|
||||
*
|
||||
* @param {goog.events.ListenableType} src The node to listen to events on.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
|
||||
* method, or an object with a handleEvent function.
|
||||
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
|
||||
* false).
|
||||
* @param {Object=} opt_scope Element in whose scope to call the listener.
|
||||
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
|
||||
* listener to.
|
||||
*/
|
||||
goog.events.EventWrapper.prototype.listen = function(
|
||||
src, listener, opt_capt, opt_scope, opt_eventHandler) {};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener added using goog.events.EventWrapper.listen.
|
||||
*
|
||||
* @param {goog.events.ListenableType} src The node to remove listener from.
|
||||
* @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
|
||||
* method, or an object with a handleEvent function.
|
||||
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
|
||||
* false).
|
||||
* @param {Object=} opt_scope Element in whose scope to call the listener.
|
||||
* @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
|
||||
* listener from.
|
||||
*/
|
||||
goog.events.EventWrapper.prototype.unlisten = function(
|
||||
src, listener, opt_capt, opt_scope, opt_eventHandler) {};
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An interface for a listenable JavaScript object.
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.Listenable');
|
||||
|
||||
goog.requireType('goog.events.EventId');
|
||||
goog.requireType('goog.events.EventLike');
|
||||
goog.requireType('goog.events.ListenableKey');
|
||||
|
||||
|
||||
/**
|
||||
* A listenable interface. A listenable is an object with the ability
|
||||
* to dispatch/broadcast events to "event listeners" registered via
|
||||
* listen/listenOnce.
|
||||
*
|
||||
* The interface allows for an event propagation mechanism similar
|
||||
* to one offered by native browser event targets, such as
|
||||
* capture/bubble mechanism, stopping propagation, and preventing
|
||||
* default actions. Capture/bubble mechanism depends on the ancestor
|
||||
* tree constructed via `#getParentEventTarget`; this tree
|
||||
* must be directed acyclic graph. The meaning of default action(s)
|
||||
* in preventDefault is specific to a particular use case.
|
||||
*
|
||||
* Implementations that do not support capture/bubble or can not have
|
||||
* a parent listenable can simply not implement any ability to set the
|
||||
* parent listenable (and have `#getParentEventTarget` return
|
||||
* null).
|
||||
*
|
||||
* Implementation of this class can be used with or independently from
|
||||
* goog.events.
|
||||
*
|
||||
* Implementation must call `#addImplementation(implClass)`.
|
||||
*
|
||||
* @interface
|
||||
* @see goog.events
|
||||
* @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
|
||||
*/
|
||||
goog.events.Listenable = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* An expando property to indicate that an object implements
|
||||
* goog.events.Listenable.
|
||||
*
|
||||
* See addImplementation/isImplementedBy.
|
||||
*
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
goog.events.Listenable.IMPLEMENTED_BY_PROP =
|
||||
'closure_listenable_' + ((Math.random() * 1e6) | 0);
|
||||
|
||||
|
||||
/**
|
||||
* Marks a given class (constructor) as an implementation of
|
||||
* Listenable, so that we can query that fact at runtime. The class
|
||||
* must have already implemented the interface.
|
||||
* @param {function(new:goog.events.Listenable,...)} cls The class constructor.
|
||||
* The corresponding class must have already implemented the interface.
|
||||
*/
|
||||
goog.events.Listenable.addImplementation = function(cls) {
|
||||
'use strict';
|
||||
cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?Object} obj The object to check.
|
||||
* @return {boolean} Whether a given instance implements Listenable. The
|
||||
* class/superclass of the instance must call addImplementation.
|
||||
*/
|
||||
goog.events.Listenable.isImplementedBy = function(obj) {
|
||||
'use strict';
|
||||
return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener. A listener can only be added once to an
|
||||
* object and if it is added again the key for the listener is
|
||||
* returned. Note that if the existing listener is a one-off listener
|
||||
* (registered via listenOnce), it will no longer be a one-off
|
||||
* listener after a call to listen().
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.listen = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener that is removed automatically after the
|
||||
* listener fired once.
|
||||
*
|
||||
* If an existing listener already exists, listenOnce will do
|
||||
* nothing. In particular, if the listener was previously registered
|
||||
* via listen(), listenOnce() will not turn the listener into a
|
||||
* one-off listener. Similarly, if there is already an existing
|
||||
* one-off listener, listenOnce does not modify the listeners (it is
|
||||
* still a once listener).
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.listenOnce = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listen() or listenOnce().
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
|
||||
* method.
|
||||
* @param {boolean=} opt_useCapture Whether to fire in capture phase
|
||||
* (defaults to false).
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call
|
||||
* the listener.
|
||||
* @return {boolean} Whether any listener was removed.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.unlisten = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an event listener which was added with listen() by the key
|
||||
* returned by listen().
|
||||
*
|
||||
* @param {!goog.events.ListenableKey} key The key returned by
|
||||
* listen() or listenOnce().
|
||||
* @return {boolean} Whether any listener was removed.
|
||||
*/
|
||||
goog.events.Listenable.prototype.unlistenByKey = function(key) {};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches an event (or event like object) and calls all listeners
|
||||
* listening for events of this type. The type of the event is decided by the
|
||||
* type property on the event object.
|
||||
*
|
||||
* If any of the listeners returns false OR calls preventDefault then this
|
||||
* function will return false. If one of the capture listeners calls
|
||||
* stopPropagation, then the bubble listeners won't fire.
|
||||
*
|
||||
* @param {?goog.events.EventLike} e Event object.
|
||||
* @return {boolean} If anyone called preventDefault on the event object (or
|
||||
* if any of the listeners returns false) this will also return false.
|
||||
*/
|
||||
goog.events.Listenable.prototype.dispatchEvent = function(e) {};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all listeners from this listenable. If type is specified,
|
||||
* it will only remove listeners of the particular type. otherwise all
|
||||
* registered listeners will be removed.
|
||||
*
|
||||
* @param {string|!goog.events.EventId=} opt_type Type of event to remove,
|
||||
* default is to remove all types.
|
||||
* @return {number} Number of listeners removed.
|
||||
*/
|
||||
goog.events.Listenable.prototype.removeAllListeners = function(opt_type) {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the parent of this event target to use for capture/bubble
|
||||
* mechanism.
|
||||
*
|
||||
* NOTE(chrishenry): The name reflects the original implementation of
|
||||
* custom event target (`goog.events.EventTarget`). We decided
|
||||
* that changing the name is not worth it.
|
||||
*
|
||||
* @return {?goog.events.Listenable} The parent EventTarget or null if
|
||||
* there is no parent.
|
||||
*/
|
||||
goog.events.Listenable.prototype.getParentEventTarget = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Fires all registered listeners in this listenable for the given
|
||||
* type and capture mode, passing them the given eventObject. This
|
||||
* does not perform actual capture/bubble. Only implementors of the
|
||||
* interface should be using this.
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
|
||||
* listeners to fire.
|
||||
* @param {boolean} capture The capture mode of the listeners to fire.
|
||||
* @param {EVENTOBJ} eventObject The event object to fire.
|
||||
* @return {boolean} Whether all listeners succeeded without
|
||||
* attempting to prevent default behavior. If any listener returns
|
||||
* false or called goog.events.Event#preventDefault, this returns
|
||||
* false.
|
||||
* @template EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.fireListeners = function(
|
||||
type, capture, eventObject) {};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all listeners in this listenable for the given type and
|
||||
* capture mode.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type The type of the listeners to fire.
|
||||
* @param {boolean} capture The capture mode of the listeners to fire.
|
||||
* @return {!Array<!goog.events.ListenableKey>} An array of registered
|
||||
* listeners.
|
||||
* @template EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.getListeners = function(type, capture) {};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the goog.events.ListenableKey for the event or null if no such
|
||||
* listener is in use.
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
|
||||
* without the 'on' prefix.
|
||||
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
|
||||
* listener function to get.
|
||||
* @param {boolean} capture Whether the listener is a capturing listener.
|
||||
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {?goog.events.ListenableKey} the found listener or null if not found.
|
||||
* @template SCOPE,EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.getListener = function(
|
||||
type, listener, capture, opt_listenerScope) {};
|
||||
|
||||
|
||||
/**
|
||||
* Whether there is any active listeners matching the specified
|
||||
* signature. If either the type or capture parameters are
|
||||
* unspecified, the function will match on the remaining criteria.
|
||||
*
|
||||
* @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
|
||||
* @param {boolean=} opt_capture Whether to check for capture or bubble
|
||||
* listeners.
|
||||
* @return {boolean} Whether there is any active listeners matching
|
||||
* the requested type and/or capture phase.
|
||||
* @template EVENTOBJ
|
||||
*/
|
||||
goog.events.Listenable.prototype.hasListener = function(
|
||||
opt_type, opt_capture) {};
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An interface that describes a single registered listener.
|
||||
*/
|
||||
goog.provide('goog.events.ListenableKey');
|
||||
|
||||
goog.requireType('goog.events.Listenable');
|
||||
|
||||
|
||||
/**
|
||||
* An interface that describes a single registered listener.
|
||||
* @interface
|
||||
*/
|
||||
goog.events.ListenableKey = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Counter used to create a unique key
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.events.ListenableKey.counter_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Reserves a key to be used for ListenableKey#key field.
|
||||
* @return {number} A number to be used to fill ListenableKey#key
|
||||
* field.
|
||||
*/
|
||||
goog.events.ListenableKey.reserveKey = function() {
|
||||
'use strict';
|
||||
return ++goog.events.ListenableKey.counter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The source event target.
|
||||
* @type {?Object|?goog.events.Listenable}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.src;
|
||||
|
||||
|
||||
/**
|
||||
* The event type the listener is listening to.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* The listener function.
|
||||
* @type {function(?):?|{handleEvent:function(?):?}|null}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.listener;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the listener works on capture phase.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.capture;
|
||||
|
||||
|
||||
/**
|
||||
* The 'this' object for the listener function's scope.
|
||||
* @type {?Object|undefined}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.handler;
|
||||
|
||||
|
||||
/**
|
||||
* A globally unique number to identify the key.
|
||||
* @type {number}
|
||||
*/
|
||||
goog.events.ListenableKey.prototype.key;
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Listener object.
|
||||
* @see ../demos/events.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.Listener');
|
||||
|
||||
goog.require('goog.events.ListenableKey');
|
||||
goog.requireType('goog.events.Listenable');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simple class that stores information about a listener
|
||||
* @param {function(?):?} listener Callback function.
|
||||
* @param {Function} proxy Wrapper for the listener that patches the event.
|
||||
* @param {EventTarget|goog.events.Listenable} src Source object for
|
||||
* the event.
|
||||
* @param {string} type Event type.
|
||||
* @param {boolean} capture Whether in capture or bubble phase.
|
||||
* @param {Object=} opt_handler Object in whose context to execute the callback.
|
||||
* @implements {goog.events.ListenableKey}
|
||||
* @constructor
|
||||
*/
|
||||
goog.events.Listener = function(
|
||||
listener, proxy, src, type, capture, opt_handler) {
|
||||
'use strict';
|
||||
if (goog.events.Listener.ENABLE_MONITORING) {
|
||||
this.creationStack = new Error().stack;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
this.listener = listener;
|
||||
|
||||
/**
|
||||
* A wrapper over the original listener. This is used solely to
|
||||
* handle native browser events (it is used to simulate the capture
|
||||
* phase and to patch the event object).
|
||||
* @type {Function}
|
||||
*/
|
||||
this.proxy = proxy;
|
||||
|
||||
/**
|
||||
* Object or node that callback is listening to
|
||||
* @type {EventTarget|goog.events.Listenable}
|
||||
*/
|
||||
this.src = src;
|
||||
|
||||
/**
|
||||
* The event type.
|
||||
* @const {string}
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* Whether the listener is being called in the capture or bubble phase
|
||||
* @const {boolean}
|
||||
*/
|
||||
this.capture = !!capture;
|
||||
|
||||
/**
|
||||
* Optional object whose context to execute the listener in
|
||||
* @type {Object|undefined}
|
||||
*/
|
||||
this.handler = opt_handler;
|
||||
|
||||
/**
|
||||
* The key of the listener.
|
||||
* @const {number}
|
||||
* @override
|
||||
*/
|
||||
this.key = goog.events.ListenableKey.reserveKey();
|
||||
|
||||
/**
|
||||
* Whether to remove the listener after it has been called.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.callOnce = false;
|
||||
|
||||
/**
|
||||
* Whether the listener has been removed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.removed = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to enable the monitoring of the
|
||||
* goog.events.Listener instances. Switching on the monitoring is only
|
||||
* recommended for debugging because it has a significant impact on
|
||||
* performance and memory usage. If switched off, the monitoring code
|
||||
* compiles down to 0 bytes.
|
||||
*/
|
||||
goog.events.Listener.ENABLE_MONITORING =
|
||||
goog.define('goog.events.Listener.ENABLE_MONITORING', false);
|
||||
|
||||
|
||||
/**
|
||||
* If monitoring the goog.events.Listener instances is enabled, stores the
|
||||
* creation stack trace of the Disposable instance.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.events.Listener.prototype.creationStack;
|
||||
|
||||
|
||||
/**
|
||||
* Marks this listener as removed. This also remove references held by
|
||||
* this listener object (such as listener and event source).
|
||||
*/
|
||||
goog.events.Listener.prototype.markAsRemoved = function() {
|
||||
'use strict';
|
||||
this.removed = true;
|
||||
this.listener = null;
|
||||
this.proxy = null;
|
||||
this.src = null;
|
||||
this.handler = null;
|
||||
};
|
||||
Executable
+310
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A map of listeners that provides utility functions to
|
||||
* deal with listeners on an event target. Used by
|
||||
* `goog.events.EventTarget`.
|
||||
*
|
||||
* WARNING: Do not use this class from outside goog.events package.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.events.ListenerMap');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.events.Listener');
|
||||
goog.require('goog.object');
|
||||
goog.requireType('goog.events.EventId');
|
||||
goog.requireType('goog.events.Listenable');
|
||||
goog.requireType('goog.events.ListenableKey');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new listener map.
|
||||
* @param {EventTarget|goog.events.Listenable} src The src object.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.events.ListenerMap = function(src) {
|
||||
'use strict';
|
||||
/** @type {EventTarget|goog.events.Listenable} */
|
||||
this.src = src;
|
||||
|
||||
/**
|
||||
* Maps of event type to an array of listeners.
|
||||
* @type {!Object<string, !Array<!goog.events.Listener>>}
|
||||
*/
|
||||
this.listeners = {};
|
||||
|
||||
/**
|
||||
* The count of types in this map that have registered listeners.
|
||||
* @private {number}
|
||||
*/
|
||||
this.typeCount_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The count of event types in this map that actually
|
||||
* have registered listeners.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.getTypeCount = function() {
|
||||
'use strict';
|
||||
return this.typeCount_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} Total number of registered listeners.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.getListenerCount = function() {
|
||||
'use strict';
|
||||
var count = 0;
|
||||
for (var type in this.listeners) {
|
||||
count += this.listeners[type].length;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener. A listener can only be added once to an
|
||||
* object and if it is added again the key for the listener is
|
||||
* returned.
|
||||
*
|
||||
* Note that a one-off listener will not change an existing listener,
|
||||
* if any. On the other hand a normal listener will change existing
|
||||
* one-off listener to become a normal listener.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type The listener event type.
|
||||
* @param {!Function} listener This listener callback method.
|
||||
* @param {boolean} callOnce Whether the listener is a one-off
|
||||
* listener.
|
||||
* @param {boolean=} opt_useCapture The capture mode of the listener.
|
||||
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.add = function(
|
||||
type, listener, callOnce, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
var typeStr = type.toString();
|
||||
var listenerArray = this.listeners[typeStr];
|
||||
if (!listenerArray) {
|
||||
listenerArray = this.listeners[typeStr] = [];
|
||||
this.typeCount_++;
|
||||
}
|
||||
|
||||
var listenerObj;
|
||||
var index = goog.events.ListenerMap.findListenerIndex_(
|
||||
listenerArray, listener, opt_useCapture, opt_listenerScope);
|
||||
if (index > -1) {
|
||||
listenerObj = listenerArray[index];
|
||||
if (!callOnce) {
|
||||
// Ensure that, if there is an existing callOnce listener, it is no
|
||||
// longer a callOnce listener.
|
||||
listenerObj.callOnce = false;
|
||||
}
|
||||
} else {
|
||||
listenerObj = new goog.events.Listener(
|
||||
listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
|
||||
listenerObj.callOnce = callOnce;
|
||||
listenerArray.push(listenerObj);
|
||||
}
|
||||
return listenerObj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a matching listener.
|
||||
* @param {string|!goog.events.EventId} type The listener event type.
|
||||
* @param {!Function} listener This listener callback method.
|
||||
* @param {boolean=} opt_useCapture The capture mode of the listener.
|
||||
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {boolean} Whether any listener was removed.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.remove = function(
|
||||
type, listener, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
var typeStr = type.toString();
|
||||
if (!(typeStr in this.listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var listenerArray = this.listeners[typeStr];
|
||||
var index = goog.events.ListenerMap.findListenerIndex_(
|
||||
listenerArray, listener, opt_useCapture, opt_listenerScope);
|
||||
if (index > -1) {
|
||||
var listenerObj = listenerArray[index];
|
||||
listenerObj.markAsRemoved();
|
||||
goog.array.removeAt(listenerArray, index);
|
||||
if (listenerArray.length == 0) {
|
||||
delete this.listeners[typeStr];
|
||||
this.typeCount_--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given listener object.
|
||||
* @param {!goog.events.ListenableKey} listener The listener to remove.
|
||||
* @return {boolean} Whether the listener is removed.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.removeByKey = function(listener) {
|
||||
'use strict';
|
||||
var type = listener.type;
|
||||
if (!(type in this.listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var removed = goog.array.remove(this.listeners[type], listener);
|
||||
if (removed) {
|
||||
/** @type {!goog.events.Listener} */ (listener).markAsRemoved();
|
||||
if (this.listeners[type].length == 0) {
|
||||
delete this.listeners[type];
|
||||
this.typeCount_--;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all listeners from this map. If opt_type is provided, only
|
||||
* listeners that match the given type are removed.
|
||||
* @param {string|!goog.events.EventId=} opt_type Type of event to remove.
|
||||
* @return {number} Number of listeners removed.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
|
||||
'use strict';
|
||||
var typeStr = opt_type && opt_type.toString();
|
||||
var count = 0;
|
||||
for (var type in this.listeners) {
|
||||
if (!typeStr || type == typeStr) {
|
||||
var listenerArray = this.listeners[type];
|
||||
for (var i = 0; i < listenerArray.length; i++) {
|
||||
++count;
|
||||
listenerArray[i].markAsRemoved();
|
||||
}
|
||||
delete this.listeners[type];
|
||||
this.typeCount_--;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all listeners that match the given type and capture mode. The
|
||||
* returned array is a copy (but the listener objects are not).
|
||||
* @param {string|!goog.events.EventId} type The type of the listeners
|
||||
* to retrieve.
|
||||
* @param {boolean} capture The capture mode of the listeners to retrieve.
|
||||
* @return {!Array<!goog.events.ListenableKey>} An array of matching
|
||||
* listeners.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
|
||||
'use strict';
|
||||
var listenerArray = this.listeners[type.toString()];
|
||||
var rv = [];
|
||||
if (listenerArray) {
|
||||
for (var i = 0; i < listenerArray.length; ++i) {
|
||||
var listenerObj = listenerArray[i];
|
||||
if (listenerObj.capture == capture) {
|
||||
rv.push(listenerObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the goog.events.ListenableKey for the event or null if no such
|
||||
* listener is in use.
|
||||
*
|
||||
* @param {string|!goog.events.EventId} type The type of the listener
|
||||
* to retrieve.
|
||||
* @param {!Function} listener The listener function to get.
|
||||
* @param {boolean} capture Whether the listener is a capturing listener.
|
||||
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||||
* listener.
|
||||
* @return {goog.events.ListenableKey} the found listener or null if not found.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.getListener = function(
|
||||
type, listener, capture, opt_listenerScope) {
|
||||
'use strict';
|
||||
var listenerArray = this.listeners[type.toString()];
|
||||
var i = -1;
|
||||
if (listenerArray) {
|
||||
i = goog.events.ListenerMap.findListenerIndex_(
|
||||
listenerArray, listener, capture, opt_listenerScope);
|
||||
}
|
||||
return i > -1 ? listenerArray[i] : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether there is a matching listener. If either the type or capture
|
||||
* parameters are unspecified, the function will match on the
|
||||
* remaining criteria.
|
||||
*
|
||||
* @param {string|!goog.events.EventId=} opt_type The type of the listener.
|
||||
* @param {boolean=} opt_capture The capture mode of the listener.
|
||||
* @return {boolean} Whether there is an active listener matching
|
||||
* the requested type and/or capture phase.
|
||||
*/
|
||||
goog.events.ListenerMap.prototype.hasListener = function(
|
||||
opt_type, opt_capture) {
|
||||
'use strict';
|
||||
var hasType = (opt_type !== undefined);
|
||||
var typeStr = hasType ? opt_type.toString() : '';
|
||||
var hasCapture = (opt_capture !== undefined);
|
||||
|
||||
return goog.object.some(this.listeners, function(listenerArray, type) {
|
||||
'use strict';
|
||||
for (var i = 0; i < listenerArray.length; ++i) {
|
||||
if ((!hasType || listenerArray[i].type == typeStr) &&
|
||||
(!hasCapture || listenerArray[i].capture == opt_capture)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the index of a matching goog.events.Listener in the given
|
||||
* listenerArray.
|
||||
* @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
|
||||
* @param {!Function} listener The listener function.
|
||||
* @param {boolean=} opt_useCapture The capture flag for the listener.
|
||||
* @param {Object=} opt_listenerScope The listener scope.
|
||||
* @return {number} The index of the matching listener within the
|
||||
* listenerArray.
|
||||
* @private
|
||||
*/
|
||||
goog.events.ListenerMap.findListenerIndex_ = function(
|
||||
listenerArray, listener, opt_useCapture, opt_listenerScope) {
|
||||
'use strict';
|
||||
for (var i = 0; i < listenerArray.length; ++i) {
|
||||
var listenerObj = listenerArray[i];
|
||||
if (!listenerObj.removed && listenerObj.listener == listener &&
|
||||
listenerObj.capture == !!opt_useCapture &&
|
||||
listenerObj.handler == opt_listenerScope) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Wrappers for the HTML5 File API. These wrappers closely mirror
|
||||
* the underlying APIs, but use Closure-style events and Deferred return values.
|
||||
* Their existence also makes it possible to mock the FileSystem API for testing
|
||||
* in browsers that don't support it natively.
|
||||
*
|
||||
* When adding public functions to anything under this namespace, be sure to add
|
||||
* its mock counterpart to goog.testing.fs.
|
||||
*/
|
||||
|
||||
goog.provide('goog.fs.blob');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates one or more values together and converts them to a Blob.
|
||||
*
|
||||
* @param {...(string|!Blob|!ArrayBuffer)} var_args The values that will make up
|
||||
* the resulting blob.
|
||||
* @return {!Blob} The blob.
|
||||
*/
|
||||
goog.fs.blob.getBlob = function(var_args) {
|
||||
'use strict';
|
||||
const BlobBuilder = goog.global.BlobBuilder || goog.global.WebKitBlobBuilder;
|
||||
|
||||
if (BlobBuilder !== undefined) {
|
||||
const bb = new BlobBuilder();
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
bb.append(arguments[i]);
|
||||
}
|
||||
return bb.getBlob();
|
||||
} else {
|
||||
return goog.fs.blob.getBlobWithProperties(
|
||||
Array.prototype.slice.call(arguments));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a blob with the given properties.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/Blob for more details.
|
||||
*
|
||||
* @param {!Array<string|!Blob|!ArrayBuffer>} parts The values that will make up
|
||||
* the resulting blob (subset supported by both BlobBuilder.append() and
|
||||
* Blob constructor).
|
||||
* @param {string=} opt_type The MIME type of the Blob.
|
||||
* @param {string=} opt_endings Specifies how strings containing newlines are to
|
||||
* be written out.
|
||||
* @return {!Blob} The blob.
|
||||
*/
|
||||
goog.fs.blob.getBlobWithProperties = function(parts, opt_type, opt_endings) {
|
||||
'use strict';
|
||||
const BlobBuilder = goog.global.BlobBuilder || goog.global.WebKitBlobBuilder;
|
||||
|
||||
if (BlobBuilder !== undefined) {
|
||||
const bb = new BlobBuilder();
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
bb.append(parts[i], opt_endings);
|
||||
}
|
||||
return bb.getBlob(opt_type);
|
||||
} else if (goog.global.Blob !== undefined) {
|
||||
const properties = {};
|
||||
if (opt_type) {
|
||||
properties['type'] = opt_type;
|
||||
}
|
||||
if (opt_endings) {
|
||||
properties['endings'] = opt_endings;
|
||||
}
|
||||
return new Blob(parts, properties);
|
||||
} else {
|
||||
throw new Error('This browser doesn\'t seem to support creating Blobs');
|
||||
}
|
||||
};
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Wrapper for URL and its createObjectUrl and revokeObjectUrl
|
||||
* methods that are part of the HTML5 File API.
|
||||
*/
|
||||
|
||||
goog.provide('goog.fs.url');
|
||||
|
||||
|
||||
/**
|
||||
* Creates a blob URL for a blob object.
|
||||
* Throws an error if the browser does not support Object Urls.
|
||||
*
|
||||
* @param {!File|!Blob|!MediaSource|!MediaStream} obj The object for which
|
||||
* to create the URL.
|
||||
* @return {string} The URL for the object.
|
||||
*/
|
||||
goog.fs.url.createObjectUrl = function(obj) {
|
||||
'use strict';
|
||||
return goog.fs.url.getUrlObject_().createObjectURL(obj);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Revokes a URL created by {@link goog.fs.url.createObjectUrl}.
|
||||
* Throws an error if the browser does not support Object Urls.
|
||||
*
|
||||
* @param {string} url The URL to revoke.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.fs.url.revokeObjectUrl = function(url) {
|
||||
'use strict';
|
||||
goog.fs.url.getUrlObject_().revokeObjectURL(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @record
|
||||
* @private
|
||||
*/
|
||||
goog.fs.url.UrlObject_ = function() {};
|
||||
|
||||
/**
|
||||
* @param {!File|!Blob|!MediaSource|!MediaStream} arg
|
||||
* @return {string}
|
||||
*/
|
||||
goog.fs.url.UrlObject_.prototype.createObjectURL = function(arg) {};
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @return {void}
|
||||
*/
|
||||
goog.fs.url.UrlObject_.prototype.revokeObjectURL = function(s) {};
|
||||
|
||||
|
||||
/**
|
||||
* Get the object that has the createObjectURL and revokeObjectURL functions for
|
||||
* this browser.
|
||||
*
|
||||
* @return {!goog.fs.url.UrlObject_} The object for this browser.
|
||||
* @private
|
||||
*/
|
||||
goog.fs.url.getUrlObject_ = function() {
|
||||
'use strict';
|
||||
const urlObject = goog.fs.url.findUrlObject_();
|
||||
if (urlObject != null) {
|
||||
return urlObject;
|
||||
} else {
|
||||
throw new Error('This browser doesn\'t seem to support blob URLs');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds the object that has the createObjectURL and revokeObjectURL functions
|
||||
* for this browser.
|
||||
*
|
||||
* @return {?goog.fs.url.UrlObject_} The object for this browser or null if the
|
||||
* browser does not support Object Urls.
|
||||
* @private
|
||||
*/
|
||||
goog.fs.url.findUrlObject_ = function() {
|
||||
'use strict';
|
||||
// This is what the spec says to do
|
||||
// http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL
|
||||
if (goog.global.URL !== undefined &&
|
||||
goog.global.URL.createObjectURL !== undefined) {
|
||||
return /** @type {!goog.fs.url.UrlObject_} */ (goog.global.URL);
|
||||
// This is what the spec used to say to do
|
||||
} else if (goog.global.createObjectURL !== undefined) {
|
||||
return /** @type {!goog.fs.url.UrlObject_} */ (goog.global);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this browser supports Object Urls. If not, calls to
|
||||
* createObjectUrl and revokeObjectUrl will result in an error.
|
||||
*
|
||||
* @return {boolean} True if this browser supports Object Urls.
|
||||
*/
|
||||
goog.fs.url.browserSupportsObjectUrls = function() {
|
||||
'use strict';
|
||||
return goog.fs.url.findUrlObject_() != null;
|
||||
};
|
||||
Executable
+584
@@ -0,0 +1,584 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for creating functions. Loosely inspired by these
|
||||
* java classes from the Guava library:
|
||||
* com.google.common.base.Functions
|
||||
* https://google.github.io/guava/releases/snapshot-jre/api/docs/index.html?com/google/common/base/Functions.html
|
||||
*
|
||||
* com.google.common.base.Predicates
|
||||
* https://google.github.io/guava/releases/snapshot-jre/api/docs/index.html?com/google/common/base/Predicates.html
|
||||
*
|
||||
* More about these can be found at
|
||||
* https://github.com/google/guava/wiki/FunctionalExplained
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.functions');
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that always returns the same value.
|
||||
* @param {T} retValue The value to return.
|
||||
* @return {function():T} The new function.
|
||||
* @template T
|
||||
*/
|
||||
goog.functions.constant = function(retValue) {
|
||||
'use strict';
|
||||
return function() {
|
||||
'use strict';
|
||||
return retValue;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Always returns false.
|
||||
* @type {function(...): boolean}
|
||||
*/
|
||||
goog.functions.FALSE = function() {
|
||||
'use strict';
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Always returns true.
|
||||
* @type {function(...): boolean}
|
||||
*/
|
||||
goog.functions.TRUE = function() {
|
||||
'use strict';
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Always returns `null`.
|
||||
* @type {function(...): null}
|
||||
*/
|
||||
goog.functions.NULL = function() {
|
||||
'use strict';
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Always returns `undefined`.
|
||||
* @type {function(...): undefined}
|
||||
*/
|
||||
goog.functions.UNDEFINED = function() {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Always returns `undefined` (loosely-typed version).
|
||||
* @type {!Function}
|
||||
*/
|
||||
goog.functions.EMPTY = /** @type {?} */ (goog.functions.UNDEFINED);
|
||||
|
||||
|
||||
/**
|
||||
* A simple function that returns the first argument of whatever is passed
|
||||
* into it.
|
||||
* @param {T=} opt_returnValue The single value that will be returned.
|
||||
* @param {...*} var_args Optional trailing arguments. These are ignored.
|
||||
* @return {T} The first argument passed in, or undefined if nothing was passed.
|
||||
* @template T
|
||||
*/
|
||||
goog.functions.identity = function(opt_returnValue, var_args) {
|
||||
'use strict';
|
||||
return opt_returnValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that always throws an error with the given message.
|
||||
* @param {string} message The error message.
|
||||
* @return {!Function} The error-throwing function.
|
||||
*/
|
||||
goog.functions.error = function(message) {
|
||||
'use strict';
|
||||
return function() {
|
||||
'use strict';
|
||||
throw new Error(message);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that throws the given object.
|
||||
* @param {*} err An object to be thrown.
|
||||
* @return {!Function} The error-throwing function.
|
||||
*/
|
||||
goog.functions.fail = function(err) {
|
||||
'use strict';
|
||||
return function() {
|
||||
'use strict';
|
||||
throw err;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given a function, create a function that keeps opt_numArgs arguments and
|
||||
* silently discards all additional arguments.
|
||||
* @param {Function} f The original function.
|
||||
* @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
|
||||
* @return {!Function} A version of f that only keeps the first opt_numArgs
|
||||
* arguments.
|
||||
*/
|
||||
goog.functions.lock = function(f, opt_numArgs) {
|
||||
'use strict';
|
||||
opt_numArgs = opt_numArgs || 0;
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
return f.apply(self, Array.prototype.slice.call(arguments, 0, opt_numArgs));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that returns its nth argument.
|
||||
* @param {number} n The position of the return argument.
|
||||
* @return {!Function} A new function.
|
||||
*/
|
||||
goog.functions.nth = function(n) {
|
||||
'use strict';
|
||||
return function() {
|
||||
'use strict';
|
||||
return arguments[n];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Like goog.partial(), except that arguments are added after arguments to the
|
||||
* returned function.
|
||||
*
|
||||
* Usage:
|
||||
* function f(arg1, arg2, arg3, arg4) { ... }
|
||||
* var g = goog.functions.partialRight(f, arg3, arg4);
|
||||
* g(arg1, arg2);
|
||||
*
|
||||
* @param {!Function} fn A function to partially apply.
|
||||
* @param {...*} var_args Additional arguments that are partially applied to fn
|
||||
* at the end.
|
||||
* @return {!Function} A partially-applied form of the function goog.partial()
|
||||
* was invoked as a method of.
|
||||
*/
|
||||
goog.functions.partialRight = function(fn, var_args) {
|
||||
'use strict';
|
||||
const rightArgs = Array.prototype.slice.call(arguments, 1);
|
||||
return function() {
|
||||
'use strict';
|
||||
// Even in strict mode, IE10/11 and Edge (non-Chromium) use global context
|
||||
// when free-calling functions. To catch cases where people were using this
|
||||
// erroneously, we explicitly change the context to undefined to match
|
||||
// strict mode specifications.
|
||||
let self = /** @type {*} */ (this);
|
||||
if (self === goog.global) {
|
||||
self = undefined;
|
||||
}
|
||||
const newArgs = Array.prototype.slice.call(arguments);
|
||||
newArgs.push.apply(newArgs, rightArgs);
|
||||
return fn.apply(self, newArgs);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given a function, create a new function that swallows its return value
|
||||
* and replaces it with a new one.
|
||||
* @param {Function} f A function.
|
||||
* @param {T} retValue A new return value.
|
||||
* @return {function(...?):T} A new function.
|
||||
* @template T
|
||||
*/
|
||||
goog.functions.withReturnValue = function(f, retValue) {
|
||||
'use strict';
|
||||
return goog.functions.sequence(f, goog.functions.constant(retValue));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that returns whether its argument equals the given value.
|
||||
*
|
||||
* Example:
|
||||
* var key = goog.object.findKey(obj, goog.functions.equalTo('needle'));
|
||||
*
|
||||
* @param {*} value The value to compare to.
|
||||
* @param {boolean=} opt_useLooseComparison Whether to use a loose (==)
|
||||
* comparison rather than a strict (===) one. Defaults to false.
|
||||
* @return {function(*):boolean} The new function.
|
||||
*/
|
||||
goog.functions.equalTo = function(value, opt_useLooseComparison) {
|
||||
'use strict';
|
||||
return function(other) {
|
||||
'use strict';
|
||||
return opt_useLooseComparison ? (value == other) : (value === other);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the composition of the functions passed in.
|
||||
* For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
|
||||
* @param {function(...?):T} fn The final function.
|
||||
* @param {...Function} var_args A list of functions.
|
||||
* @return {function(...?):T} The composition of all inputs.
|
||||
* @template T
|
||||
*/
|
||||
goog.functions.compose = function(fn, var_args) {
|
||||
'use strict';
|
||||
const functions = arguments;
|
||||
const length = functions.length;
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
let result;
|
||||
if (length) {
|
||||
result = functions[length - 1].apply(self, arguments);
|
||||
}
|
||||
|
||||
for (let i = length - 2; i >= 0; i--) {
|
||||
result = functions[i].call(self, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that calls the functions passed in in sequence, and
|
||||
* returns the value of the last function. For example,
|
||||
* (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
|
||||
* @param {...Function} var_args A list of functions.
|
||||
* @return {!Function} A function that calls all inputs in sequence.
|
||||
*/
|
||||
goog.functions.sequence = function(var_args) {
|
||||
'use strict';
|
||||
const functions = arguments;
|
||||
const length = functions.length;
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
let result;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result = functions[i].apply(self, arguments);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that returns true if each of its components evaluates
|
||||
* to true. The components are evaluated in order, and the evaluation will be
|
||||
* short-circuited as soon as a function returns false.
|
||||
* For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
|
||||
* @param {...Function} var_args A list of functions.
|
||||
* @return {function(...?):boolean} A function that ANDs its component
|
||||
* functions.
|
||||
*/
|
||||
goog.functions.and = function(var_args) {
|
||||
'use strict';
|
||||
const functions = arguments;
|
||||
const length = functions.length;
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (!functions[i].apply(self, arguments)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that returns true if any of its components evaluates
|
||||
* to true. The components are evaluated in order, and the evaluation will be
|
||||
* short-circuited as soon as a function returns true.
|
||||
* For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
|
||||
* @param {...Function} var_args A list of functions.
|
||||
* @return {function(...?):boolean} A function that ORs its component
|
||||
* functions.
|
||||
*/
|
||||
goog.functions.or = function(var_args) {
|
||||
'use strict';
|
||||
const functions = arguments;
|
||||
const length = functions.length;
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (functions[i].apply(self, arguments)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that returns the Boolean opposite of a provided function.
|
||||
* For example, (goog.functions.not(f))(x) is equivalent to !f(x).
|
||||
* @param {!Function} f The original function.
|
||||
* @return {function(...?):boolean} A function that delegates to f and returns
|
||||
* opposite.
|
||||
*/
|
||||
goog.functions.not = function(f) {
|
||||
'use strict';
|
||||
return function() {
|
||||
'use strict';
|
||||
const self = /** @type {*} */ (this);
|
||||
return !f.apply(self, arguments);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic factory function to construct an object given the constructor
|
||||
* and the arguments. Intended to be bound to create object factories.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var factory = goog.partial(goog.functions.create, Class);
|
||||
*
|
||||
* @param {function(new:T, ...)} constructor The constructor for the Object.
|
||||
* @param {...*} var_args The arguments to be passed to the constructor.
|
||||
* @return {T} A new instance of the class given in `constructor`.
|
||||
* @template T
|
||||
* @deprecated This function does not work with ES6 class constructors. Use
|
||||
* arrow functions + spread args instead.
|
||||
*/
|
||||
goog.functions.create = function(constructor, var_args) {
|
||||
'use strict';
|
||||
/**
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
const temp = function() {};
|
||||
temp.prototype = constructor.prototype;
|
||||
|
||||
// obj will have constructor's prototype in its chain and
|
||||
// 'obj instanceof constructor' will be true.
|
||||
const obj = new temp();
|
||||
|
||||
// obj is initialized by constructor.
|
||||
// arguments is only array-like so lacks shift(), but can be used with
|
||||
// the Array prototype function.
|
||||
constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether the return value cache should be used.
|
||||
* This should only be used to disable caches when testing.
|
||||
*/
|
||||
goog.functions.CACHE_RETURN_VALUE =
|
||||
goog.define('goog.functions.CACHE_RETURN_VALUE', true);
|
||||
|
||||
|
||||
/**
|
||||
* Gives a wrapper function that caches the return value of a parameterless
|
||||
* function when first called.
|
||||
*
|
||||
* When called for the first time, the given function is called and its
|
||||
* return value is cached (thus this is only appropriate for idempotent
|
||||
* functions). Subsequent calls will return the cached return value. This
|
||||
* allows the evaluation of expensive functions to be delayed until first used.
|
||||
*
|
||||
* To cache the return values of functions with parameters, see goog.memoize.
|
||||
*
|
||||
* @param {function():T} fn A function to lazily evaluate.
|
||||
* @return {function():T} A wrapped version the function.
|
||||
* @template T
|
||||
*/
|
||||
goog.functions.cacheReturnValue = function(fn) {
|
||||
'use strict';
|
||||
let called = false;
|
||||
let value;
|
||||
|
||||
return function() {
|
||||
'use strict';
|
||||
if (!goog.functions.CACHE_RETURN_VALUE) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
if (!called) {
|
||||
value = fn();
|
||||
called = true;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a function to allow it to be called, at most, once. All
|
||||
* additional calls are no-ops.
|
||||
*
|
||||
* This is particularly useful for initialization functions
|
||||
* that should be called, at most, once.
|
||||
*
|
||||
* @param {function():*} f Function to call.
|
||||
* @return {function():undefined} Wrapped function.
|
||||
*/
|
||||
goog.functions.once = function(f) {
|
||||
'use strict';
|
||||
// Keep a reference to the function that we null out when we're done with
|
||||
// it -- that way, the function can be GC'd when we're done with it.
|
||||
let inner = f;
|
||||
return function() {
|
||||
'use strict';
|
||||
if (inner) {
|
||||
const tmp = inner;
|
||||
inner = null;
|
||||
tmp();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a function to allow it to be called, at most, once per interval
|
||||
* (specified in milliseconds). If the wrapper function is called N times within
|
||||
* that interval, only the Nth call will go through.
|
||||
*
|
||||
* This is particularly useful for batching up repeated actions where the
|
||||
* last action should win. This can be used, for example, for refreshing an
|
||||
* autocomplete pop-up every so often rather than updating with every keystroke,
|
||||
* since the final text typed by the user is the one that should produce the
|
||||
* final autocomplete results. For more stateful debouncing with support for
|
||||
* pausing, resuming, and canceling debounced actions, use
|
||||
* `goog.async.Debouncer`.
|
||||
*
|
||||
* @param {function(this:SCOPE, ...?)} f Function to call.
|
||||
* @param {number} interval Interval over which to debounce. The function will
|
||||
* only be called after the full interval has elapsed since the last call.
|
||||
* @param {SCOPE=} opt_scope Object in whose scope to call the function.
|
||||
* @return {function(...?): undefined} Wrapped function.
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.functions.debounce = function(f, interval, opt_scope) {
|
||||
'use strict';
|
||||
let timeout = 0;
|
||||
return /** @type {function(...?)} */ (function(var_args) {
|
||||
'use strict';
|
||||
goog.global.clearTimeout(timeout);
|
||||
const args = arguments;
|
||||
timeout = goog.global.setTimeout(function() {
|
||||
'use strict';
|
||||
f.apply(opt_scope, args);
|
||||
}, interval);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a function to allow it to be called, at most, once per interval
|
||||
* (specified in milliseconds). If the wrapper function is called N times in
|
||||
* that interval, both the 1st and the Nth calls will go through.
|
||||
*
|
||||
* This is particularly useful for limiting repeated user requests where the
|
||||
* the last action should win, but you also don't want to wait until the end of
|
||||
* the interval before sending a request out, as it leads to a perception of
|
||||
* slowness for the user.
|
||||
*
|
||||
* @param {function(this:SCOPE, ...?)} f Function to call.
|
||||
* @param {number} interval Interval over which to throttle. The function can
|
||||
* only be called once per interval.
|
||||
* @param {SCOPE=} opt_scope Object in whose scope to call the function.
|
||||
* @return {function(...?): undefined} Wrapped function.
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.functions.throttle = function(f, interval, opt_scope) {
|
||||
'use strict';
|
||||
let timeout = 0;
|
||||
let shouldFire = false;
|
||||
let storedArgs = [];
|
||||
|
||||
const handleTimeout = function() {
|
||||
'use strict';
|
||||
timeout = 0;
|
||||
if (shouldFire) {
|
||||
shouldFire = false;
|
||||
fire();
|
||||
}
|
||||
};
|
||||
|
||||
const fire = function() {
|
||||
'use strict';
|
||||
timeout = goog.global.setTimeout(handleTimeout, interval);
|
||||
let args = storedArgs;
|
||||
storedArgs = []; // Avoid a space leak by clearing stored arguments.
|
||||
f.apply(opt_scope, args);
|
||||
};
|
||||
|
||||
return /** @type {function(...?)} */ (function(var_args) {
|
||||
'use strict';
|
||||
storedArgs = arguments;
|
||||
if (!timeout) {
|
||||
fire();
|
||||
} else {
|
||||
shouldFire = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a function to allow it to be called, at most, once per interval
|
||||
* (specified in milliseconds). If the wrapper function is called N times within
|
||||
* that interval, only the 1st call will go through.
|
||||
*
|
||||
* This is particularly useful for limiting repeated user requests where the
|
||||
* first request is guaranteed to have all the data required to perform the
|
||||
* final action, so there's no need to wait until the end of the interval before
|
||||
* sending the request out.
|
||||
*
|
||||
* @param {function(this:SCOPE, ...?)} f Function to call.
|
||||
* @param {number} interval Interval over which to rate-limit. The function will
|
||||
* only be called once per interval, and ignored for the remainer of the
|
||||
* interval.
|
||||
* @param {SCOPE=} opt_scope Object in whose scope to call the function.
|
||||
* @return {function(...?): undefined} Wrapped function.
|
||||
* @template SCOPE
|
||||
*/
|
||||
goog.functions.rateLimit = function(f, interval, opt_scope) {
|
||||
'use strict';
|
||||
let timeout = 0;
|
||||
|
||||
const handleTimeout = function() {
|
||||
'use strict';
|
||||
timeout = 0;
|
||||
};
|
||||
|
||||
return /** @type {function(...?)} */ (function(var_args) {
|
||||
'use strict';
|
||||
if (!timeout) {
|
||||
timeout = goog.global.setTimeout(handleTimeout, interval);
|
||||
f.apply(opt_scope, arguments);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is a function.
|
||||
* @param {*} val Variable to test.
|
||||
* @return {boolean} Whether variable is a function.
|
||||
*/
|
||||
goog.functions.isFunction = (val) => {
|
||||
return typeof val === 'function';
|
||||
};
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The event object dispatched when the history changes.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.history.Event');
|
||||
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.history.EventType');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event object dispatched after the history state has changed.
|
||||
* @param {string} token The string identifying the new history state.
|
||||
* @param {boolean} isNavigation True if the event was triggered by a browser
|
||||
* action, such as forward or back, clicking on a link, editing the URL, or
|
||||
* calling {@code window.history.(go|back|forward)}.
|
||||
* False if the token has been changed by a `setToken` or
|
||||
* `replaceToken` call.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
* @final
|
||||
*/
|
||||
goog.history.Event = function(token, isNavigation) {
|
||||
'use strict';
|
||||
goog.events.Event.call(this, goog.history.EventType.NAVIGATE);
|
||||
|
||||
/**
|
||||
* The current history state.
|
||||
* @type {string}
|
||||
*/
|
||||
this.token = token;
|
||||
|
||||
/**
|
||||
* Whether the event was triggered by browser navigation.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isNavigation = isNavigation;
|
||||
};
|
||||
goog.inherits(goog.history.Event, goog.events.Event);
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Event types for goog.history.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.history.EventType');
|
||||
|
||||
|
||||
/**
|
||||
* Event types for goog.history.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.history.EventType = {
|
||||
NAVIGATE: 'navigate'
|
||||
};
|
||||
Executable
+1006
File diff suppressed because it is too large
Load Diff
Executable
+1085
File diff suppressed because it is too large
Load Diff
Executable
+238
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The SafeScript type and its builders.
|
||||
*
|
||||
* TODO(xtof): Link to document stating type contract.
|
||||
*/
|
||||
|
||||
goog.module('goog.html.SafeScript');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const Const = goog.require('goog.string.Const');
|
||||
const TypedString = goog.require('goog.string.TypedString');
|
||||
const trustedtypes = goog.require('goog.html.trustedtypes');
|
||||
const {fail} = goog.require('goog.asserts');
|
||||
|
||||
/**
|
||||
* Token used to ensure that object is created only from this file. No code
|
||||
* outside of this file can access this token.
|
||||
* @const {!Object}
|
||||
*/
|
||||
const CONSTRUCTOR_TOKEN_PRIVATE = {};
|
||||
|
||||
/**
|
||||
* A string-like object which represents JavaScript code and that carries the
|
||||
* security type contract that its value, as a string, will not cause execution
|
||||
* of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
|
||||
* in a browser.
|
||||
*
|
||||
* Instances of this type must be created via the factory method
|
||||
* `SafeScript.fromConstant` and not by invoking its constructor. The
|
||||
* constructor intentionally takes an extra parameter that cannot be constructed
|
||||
* outside of this file and the type is immutable; hence only a default instance
|
||||
* corresponding to the empty string can be obtained via constructor invocation.
|
||||
*
|
||||
* A SafeScript's string representation can safely be interpolated as the
|
||||
* content of a script element within HTML. The SafeScript string should not be
|
||||
* escaped before interpolation.
|
||||
*
|
||||
* Note that the SafeScript might contain text that is attacker-controlled but
|
||||
* that text should have been interpolated with appropriate escaping,
|
||||
* sanitization and/or validation into the right location in the script, such
|
||||
* that it is highly constrained in its effect (for example, it had to match a
|
||||
* set of whitelisted words).
|
||||
*
|
||||
* A SafeScript can be constructed via security-reviewed unchecked
|
||||
* conversions. In this case producers of SafeScript must ensure themselves that
|
||||
* the SafeScript does not contain unsafe script. Note in particular that
|
||||
* `<` is dangerous, even when inside JavaScript strings, and so should
|
||||
* always be forbidden or JavaScript escaped in user controlled input. For
|
||||
* example, if `</script><script>evil</script>"` were
|
||||
* interpolated inside a JavaScript string, it would break out of the context
|
||||
* of the original script element and `evil` would execute. Also note
|
||||
* that within an HTML script (raw text) element, HTML character references,
|
||||
* such as "<" are not allowed. See
|
||||
* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
|
||||
* Creating SafeScript objects HAS SIDE-EFFECTS due to calling Trusted Types Web
|
||||
* API.
|
||||
*
|
||||
* @see SafeScript#fromConstant
|
||||
* @final
|
||||
* @implements {TypedString}
|
||||
*/
|
||||
class SafeScript {
|
||||
/**
|
||||
* @param {!TrustedScript|string} value
|
||||
* @param {!Object} token package-internal implementation detail.
|
||||
*/
|
||||
constructor(value, token) {
|
||||
/**
|
||||
* The contained value of this SafeScript. The field has a purposely ugly
|
||||
* name to make (non-compiled) code that attempts to directly access this
|
||||
* field stand out.
|
||||
* @private {!TrustedScript|string}
|
||||
*/
|
||||
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ =
|
||||
(token === CONSTRUCTOR_TOKEN_PRIVATE) ? value : '';
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
this.implementsGoogStringTypedString = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SafeScript object from a compile-time constant string.
|
||||
*
|
||||
* @param {!Const} script A compile-time-constant string from which to create
|
||||
* a SafeScript.
|
||||
* @return {!SafeScript} A SafeScript object initialized to `script`.
|
||||
*/
|
||||
static fromConstant(script) {
|
||||
const scriptString = Const.unwrap(script);
|
||||
if (scriptString.length === 0) {
|
||||
return SafeScript.EMPTY;
|
||||
}
|
||||
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
|
||||
scriptString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SafeScript JSON representation from anything that could be passed
|
||||
* to JSON.stringify.
|
||||
* @param {*} val
|
||||
* @return {!SafeScript}
|
||||
*/
|
||||
static fromJson(val) {
|
||||
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
|
||||
SafeScript.stringify_(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this SafeScript's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security relevant that an object's type is
|
||||
* indeed `SafeScript`, use `SafeScript.unwrap` instead of
|
||||
* this method. If in doubt, assume that it's security relevant. In
|
||||
* particular, note that goog.html functions which return a goog.html type do
|
||||
* not guarantee the returned instance is of the right type. For example:
|
||||
*
|
||||
* <pre>
|
||||
* var fakeSafeHtml = new String('fake');
|
||||
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
|
||||
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
|
||||
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
|
||||
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
|
||||
* // instanceof goog.html.SafeHtml.
|
||||
* </pre>
|
||||
*
|
||||
* @see SafeScript#unwrap
|
||||
* @override
|
||||
*/
|
||||
getTypedStringValue() {
|
||||
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed a
|
||||
* SafeScript object, and returns its value.
|
||||
*
|
||||
* @param {!SafeScript} safeScript The object to extract from.
|
||||
* @return {string} The safeScript object's contained string, unless
|
||||
* the run-time type check fails. In that case, `unwrap` returns an
|
||||
* innocuous string, or, if assertions are enabled, throws
|
||||
* `asserts.AssertionError`.
|
||||
*/
|
||||
static unwrap(safeScript) {
|
||||
return SafeScript.unwrapTrustedScript(safeScript).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps value as TrustedScript if supported or as a string if not.
|
||||
* @param {!SafeScript} safeScript
|
||||
* @return {!TrustedScript|string}
|
||||
* @see SafeScript.unwrap
|
||||
*/
|
||||
static unwrapTrustedScript(safeScript) {
|
||||
// Perform additional Run-time type-checking to ensure that
|
||||
// safeScript is indeed an instance of the expected type. This
|
||||
// provides some additional protection against security bugs due to
|
||||
// application code that disables type checks.
|
||||
// Specifically, the following checks are performed:
|
||||
// 1. The object is an instance of the expected type.
|
||||
// 2. The object is not an instance of a subclass.
|
||||
if (safeScript instanceof SafeScript &&
|
||||
safeScript.constructor === SafeScript) {
|
||||
return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
|
||||
} else {
|
||||
fail(
|
||||
'expected object of type SafeScript, got \'' + safeScript +
|
||||
'\' of type ' + goog.typeOf(safeScript));
|
||||
return 'type_error:SafeScript';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value to an embeddable JSON string and returns it. The
|
||||
* resulting string can be embedded in HTML because the '<' character is
|
||||
* encoded.
|
||||
*
|
||||
* @param {*} val
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
static stringify_(val) {
|
||||
const json = JSON.stringify(val);
|
||||
return json.replace(/</g, '\\x3c');
|
||||
}
|
||||
|
||||
/**
|
||||
* Package-internal utility method to create SafeScript instances.
|
||||
*
|
||||
* @param {string} script The string to initialize the SafeScript object with.
|
||||
* @return {!SafeScript} The initialized SafeScript object.
|
||||
* @package
|
||||
*/
|
||||
static createSafeScriptSecurityPrivateDoNotAccessOrElse(script) {
|
||||
const policy = trustedtypes.getPolicyPrivateDoNotAccessOrElse();
|
||||
const trustedScript = policy ? policy.createScript(script) : script;
|
||||
return new SafeScript(trustedScript, CONSTRUCTOR_TOKEN_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string-representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped in a SafeScript, use
|
||||
* `SafeScript.unwrap`.
|
||||
*
|
||||
* @return {string}
|
||||
* @see SafeScript#unwrap
|
||||
* @override
|
||||
*/
|
||||
SafeScript.prototype.toString = function() {
|
||||
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A SafeScript instance corresponding to the empty string.
|
||||
* @const {!SafeScript}
|
||||
*/
|
||||
SafeScript.EMPTY = /** @type {!SafeScript} */ ({
|
||||
// NOTE: this compiles to nothing, but hides the possible side effect of
|
||||
// SafeScript creation (due to calling trustedTypes.createPolicy) from the
|
||||
// compiler so that the entire call can be removed if the result is not used.
|
||||
valueOf: function() {
|
||||
return SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
|
||||
},
|
||||
}.valueOf());
|
||||
|
||||
|
||||
exports = SafeScript;
|
||||
Executable
+585
@@ -0,0 +1,585 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The SafeStyle type and its builders.
|
||||
*
|
||||
* TODO(xtof): Link to document stating type contract.
|
||||
*/
|
||||
|
||||
goog.module('goog.html.SafeStyle');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const Const = goog.require('goog.string.Const');
|
||||
const SafeUrl = goog.require('goog.html.SafeUrl');
|
||||
const TypedString = goog.require('goog.string.TypedString');
|
||||
const {AssertionError, assert, fail} = goog.require('goog.asserts');
|
||||
const {contains, endsWith} = goog.require('goog.string.internal');
|
||||
|
||||
/**
|
||||
* Token used to ensure that object is created only from this file. No code
|
||||
* outside of this file can access this token.
|
||||
* @type {!Object}
|
||||
* @const
|
||||
*/
|
||||
const CONSTRUCTOR_TOKEN_PRIVATE = {};
|
||||
|
||||
/**
|
||||
* A string-like object which represents a sequence of CSS declarations
|
||||
* (`propertyName1: propertyvalue1; propertyName2: propertyValue2; ...`)
|
||||
* and that carries the security type contract that its value, as a string,
|
||||
* will not cause untrusted script execution (XSS) when evaluated as CSS in a
|
||||
* browser.
|
||||
*
|
||||
* Instances of this type must be created via the factory methods
|
||||
* (`SafeStyle.create` or `SafeStyle.fromConstant`)
|
||||
* and not by invoking its constructor. The constructor intentionally takes an
|
||||
* extra parameter that cannot be constructed outside of this file and the type
|
||||
* is immutable; hence only a default instance corresponding to the empty string
|
||||
* can be obtained via constructor invocation.
|
||||
*
|
||||
* SafeStyle's string representation can safely be:
|
||||
* <ul>
|
||||
* <li>Interpolated as the content of a *quoted* HTML style attribute.
|
||||
* However, the SafeStyle string *must be HTML-attribute-escaped* before
|
||||
* interpolation.
|
||||
* <li>Interpolated as the content of a {}-wrapped block within a stylesheet.
|
||||
* '<' characters in the SafeStyle string *must be CSS-escaped* before
|
||||
* interpolation. The SafeStyle string is also guaranteed not to be able
|
||||
* to introduce new properties or elide existing ones.
|
||||
* <li>Interpolated as the content of a {}-wrapped block within an HTML
|
||||
* <style> element. '<' characters in the SafeStyle string
|
||||
* *must be CSS-escaped* before interpolation.
|
||||
* <li>Assigned to the style property of a DOM node. The SafeStyle string
|
||||
* should not be escaped before being assigned to the property.
|
||||
* </ul>
|
||||
*
|
||||
* A SafeStyle may never contain literal angle brackets. Otherwise, it could
|
||||
* be unsafe to place a SafeStyle into a <style> tag (where it can't
|
||||
* be HTML escaped). For example, if the SafeStyle containing
|
||||
* `font: 'foo <style/><script>evil</script>'` were
|
||||
* interpolated within a <style> tag, this would then break out of the
|
||||
* style context into HTML.
|
||||
*
|
||||
* A SafeStyle may contain literal single or double quotes, and as such the
|
||||
* entire style string must be escaped when used in a style attribute (if
|
||||
* this were not the case, the string could contain a matching quote that
|
||||
* would escape from the style attribute).
|
||||
*
|
||||
* Values of this type must be composable, i.e. for any two values
|
||||
* `style1` and `style2` of this type,
|
||||
* `SafeStyle.unwrap(style1) +
|
||||
* SafeStyle.unwrap(style2)` must itself be a value that satisfies
|
||||
* the SafeStyle type constraint. This requirement implies that for any value
|
||||
* `style` of this type, `SafeStyle.unwrap(style)` must
|
||||
* not end in a "property value" or "property name" context. For example,
|
||||
* a value of `background:url("` or `font-` would not satisfy the
|
||||
* SafeStyle contract. This is because concatenating such strings with a
|
||||
* second value that itself does not contain unsafe CSS can result in an
|
||||
* overall string that does. For example, if `javascript:evil())"` is
|
||||
* appended to `background:url("}, the resulting string may result in
|
||||
* the execution of a malicious script.
|
||||
*
|
||||
* TODO(mlourenco): Consider whether we should implement UTF-8 interchange
|
||||
* validity checks and blacklisting of newlines (including Unicode ones) and
|
||||
* other whitespace characters (\t, \f). Document here if so and also update
|
||||
* SafeStyle.fromConstant().
|
||||
*
|
||||
* The following example values comply with this type's contract:
|
||||
* <ul>
|
||||
* <li><pre>width: 1em;</pre>
|
||||
* <li><pre>height:1em;</pre>
|
||||
* <li><pre>width: 1em;height: 1em;</pre>
|
||||
* <li><pre>background:url('http://url');</pre>
|
||||
* </ul>
|
||||
* In addition, the empty string is safe for use in a CSS attribute.
|
||||
*
|
||||
* The following example values do NOT comply with this type's contract:
|
||||
* <ul>
|
||||
* <li><pre>background: red</pre> (missing a trailing semi-colon)
|
||||
* <li><pre>background:</pre> (missing a value and a trailing semi-colon)
|
||||
* <li><pre>1em</pre> (missing an attribute name, which provides context for
|
||||
* the value)
|
||||
* </ul>
|
||||
*
|
||||
* @see SafeStyle#create
|
||||
* @see SafeStyle#fromConstant
|
||||
* @see http://www.w3.org/TR/css3-syntax/
|
||||
* @final
|
||||
* @struct
|
||||
* @implements {TypedString}
|
||||
*/
|
||||
class SafeStyle {
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {!Object} token package-internal implementation detail.
|
||||
*/
|
||||
constructor(value, token) {
|
||||
/**
|
||||
* The contained value of this SafeStyle. The field has a purposely
|
||||
* ugly name to make (non-compiled) code that attempts to directly access
|
||||
* this field stand out.
|
||||
* @private {string}
|
||||
*/
|
||||
this.privateDoNotAccessOrElseSafeStyleWrappedValue_ =
|
||||
(token === CONSTRUCTOR_TOKEN_PRIVATE) ? value : '';
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const {boolean}
|
||||
*/
|
||||
this.implementsGoogStringTypedString = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeStyle object from a compile-time constant string.
|
||||
*
|
||||
* `style` should be in the format
|
||||
* `name: value; [name: value; ...]` and must not have any < or >
|
||||
* characters in it. This is so that SafeStyle's contract is preserved,
|
||||
* allowing the SafeStyle to correctly be interpreted as a sequence of CSS
|
||||
* declarations and without affecting the syntactic structure of any
|
||||
* surrounding CSS and HTML.
|
||||
*
|
||||
* This method performs basic sanity checks on the format of `style`
|
||||
* but does not constrain the format of `name` and `value`, except
|
||||
* for disallowing tag characters.
|
||||
*
|
||||
* @param {!Const} style A compile-time-constant string from which
|
||||
* to create a SafeStyle.
|
||||
* @return {!SafeStyle} A SafeStyle object initialized to
|
||||
* `style`.
|
||||
*/
|
||||
static fromConstant(style) {
|
||||
'use strict';
|
||||
const styleString = Const.unwrap(style);
|
||||
if (styleString.length === 0) {
|
||||
return SafeStyle.EMPTY;
|
||||
}
|
||||
assert(
|
||||
endsWith(styleString, ';'),
|
||||
`Last character of style string is not ';': ${styleString}`);
|
||||
assert(
|
||||
contains(styleString, ':'),
|
||||
'Style string must contain at least one \':\', to ' +
|
||||
'specify a "name: value" pair: ' + styleString);
|
||||
return SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
|
||||
styleString);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this SafeStyle's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security relevant that an object's type is
|
||||
* indeed `SafeStyle`, use `SafeStyle.unwrap` instead of
|
||||
* this method. If in doubt, assume that it's security relevant. In
|
||||
* particular, note that goog.html functions which return a goog.html type do
|
||||
* not guarantee the returned instance is of the right type. For example:
|
||||
*
|
||||
* <pre>
|
||||
* var fakeSafeHtml = new String('fake');
|
||||
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
|
||||
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
|
||||
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
|
||||
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
|
||||
* // instanceof goog.html.SafeHtml.
|
||||
* </pre>
|
||||
*
|
||||
* @return {string}
|
||||
* @see SafeStyle#unwrap
|
||||
* @override
|
||||
*/
|
||||
getTypedStringValue() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseSafeStyleWrappedValue_;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string-representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped in a SafeStyle, use
|
||||
* `SafeStyle.unwrap`.
|
||||
*
|
||||
* @return {string}
|
||||
* @see SafeStyle#unwrap
|
||||
* @override
|
||||
*/
|
||||
toString() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseSafeStyleWrappedValue_.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed a
|
||||
* SafeStyle object, and returns its value.
|
||||
*
|
||||
* @param {!SafeStyle} safeStyle The object to extract from.
|
||||
* @return {string} The safeStyle object's contained string, unless
|
||||
* the run-time type check fails. In that case, `unwrap` returns an
|
||||
* innocuous string, or, if assertions are enabled, throws
|
||||
* `AssertionError`.
|
||||
*/
|
||||
static unwrap(safeStyle) {
|
||||
'use strict';
|
||||
// Perform additional Run-time type-checking to ensure that
|
||||
// safeStyle is indeed an instance of the expected type. This
|
||||
// provides some additional protection against security bugs due to
|
||||
// application code that disables type checks.
|
||||
// Specifically, the following checks are performed:
|
||||
// 1. The object is an instance of the expected type.
|
||||
// 2. The object is not an instance of a subclass.
|
||||
if (safeStyle instanceof SafeStyle && safeStyle.constructor === SafeStyle) {
|
||||
return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
|
||||
} else {
|
||||
fail(
|
||||
`expected object of type SafeStyle, got '${safeStyle}` +
|
||||
'\' of type ' + goog.typeOf(safeStyle));
|
||||
return 'type_error:SafeStyle';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Package-internal utility method to create SafeStyle instances.
|
||||
*
|
||||
* @param {string} style The string to initialize the SafeStyle object with.
|
||||
* @return {!SafeStyle} The initialized SafeStyle object.
|
||||
* @package
|
||||
*/
|
||||
static createSafeStyleSecurityPrivateDoNotAccessOrElse(style) {
|
||||
'use strict';
|
||||
return new SafeStyle(style, CONSTRUCTOR_TOKEN_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SafeStyle object from the properties specified in the map.
|
||||
* @param {!SafeStyle.PropertyMap} map Mapping of property names to
|
||||
* their values, for example {'margin': '1px'}. Names must consist of
|
||||
* [-_a-zA-Z0-9]. Values might be strings consisting of
|
||||
* [-,.'"%_!# a-zA-Z0-9[\]], where ", ', and [] must be properly balanced.
|
||||
* We also allow simple functions like rgb() and url() which sanitizes its
|
||||
* contents. Other values must be wrapped in Const. URLs might
|
||||
* be passed as SafeUrl which will be wrapped into url(""). We
|
||||
* also support array whose elements are joined with ' '. Null value
|
||||
* causes skipping the property.
|
||||
* @return {!SafeStyle}
|
||||
* @throws {!Error} If invalid name is provided.
|
||||
* @throws {!AssertionError} If invalid value is provided. With
|
||||
* disabled assertions, invalid value is replaced by
|
||||
* SafeStyle.INNOCUOUS_STRING.
|
||||
*/
|
||||
static create(map) {
|
||||
'use strict';
|
||||
let style = '';
|
||||
for (let name in map) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty#Using_hasOwnProperty_as_a_property_name
|
||||
if (Object.prototype.hasOwnProperty.call(map, name)) {
|
||||
if (!/^[-_a-zA-Z0-9]+$/.test(name)) {
|
||||
throw new Error(`Name allows only [-_a-zA-Z0-9], got: ${name}`);
|
||||
}
|
||||
let value = map[name];
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value = value.map(sanitizePropertyValue).join(' ');
|
||||
} else {
|
||||
value = sanitizePropertyValue(value);
|
||||
}
|
||||
style += `${name}:${value};`;
|
||||
}
|
||||
}
|
||||
if (!style) {
|
||||
return SafeStyle.EMPTY;
|
||||
}
|
||||
return SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new SafeStyle object by concatenating the values.
|
||||
* @param {...(!SafeStyle|!Array<!SafeStyle>)} var_args
|
||||
* SafeStyles to concatenate.
|
||||
* @return {!SafeStyle}
|
||||
*/
|
||||
static concat(var_args) {
|
||||
'use strict';
|
||||
let style = '';
|
||||
|
||||
/**
|
||||
* @param {!SafeStyle|!Array<!SafeStyle>} argument
|
||||
*/
|
||||
const addArgument = argument => {
|
||||
'use strict';
|
||||
if (Array.isArray(argument)) {
|
||||
argument.forEach(addArgument);
|
||||
} else {
|
||||
style += SafeStyle.unwrap(argument);
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.forEach.call(arguments, addArgument);
|
||||
if (!style) {
|
||||
return SafeStyle.EMPTY;
|
||||
}
|
||||
return SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(style);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A SafeStyle instance corresponding to the empty string.
|
||||
* @const {!SafeStyle}
|
||||
*/
|
||||
SafeStyle.EMPTY = SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
|
||||
|
||||
|
||||
/**
|
||||
* The innocuous string generated by SafeStyle.create when passed
|
||||
* an unsafe value.
|
||||
* @const {string}
|
||||
*/
|
||||
SafeStyle.INNOCUOUS_STRING = 'zClosurez';
|
||||
|
||||
|
||||
/**
|
||||
* A single property value.
|
||||
* @typedef {string|!Const|!SafeUrl}
|
||||
*/
|
||||
SafeStyle.PropertyValue;
|
||||
|
||||
|
||||
/**
|
||||
* Mapping of property names to their values.
|
||||
* We don't support numbers even though some values might be numbers (e.g.
|
||||
* line-height or 0 for any length). The reason is that most numeric values need
|
||||
* units (e.g. '1px') and allowing numbers could cause users forgetting about
|
||||
* them.
|
||||
* @typedef {!Object<string, ?SafeStyle.PropertyValue|
|
||||
* ?Array<!SafeStyle.PropertyValue>>}
|
||||
*/
|
||||
SafeStyle.PropertyMap;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks and converts value to string.
|
||||
* @param {!SafeStyle.PropertyValue} value
|
||||
* @return {string}
|
||||
*/
|
||||
function sanitizePropertyValue(value) {
|
||||
'use strict';
|
||||
if (value instanceof SafeUrl) {
|
||||
const url = SafeUrl.unwrap(value);
|
||||
return 'url("' + url.replace(/</g, '%3c').replace(/[\\"]/g, '\\$&') + '")';
|
||||
}
|
||||
const result = value instanceof Const ?
|
||||
Const.unwrap(value) :
|
||||
sanitizePropertyValueString(String(value));
|
||||
// These characters can be used to change context and we don't want that even
|
||||
// with const values.
|
||||
if (/[{;}]/.test(result)) {
|
||||
throw new AssertionError('Value does not allow [{;}], got: %s.', [result]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks string value.
|
||||
* @param {string} value
|
||||
* @return {string}
|
||||
*/
|
||||
function sanitizePropertyValueString(value) {
|
||||
'use strict';
|
||||
// Some CSS property values permit nested functions. We allow one level of
|
||||
// nesting, and all nested functions must also be in the FUNCTIONS_RE_ list.
|
||||
const valueWithoutFunctions = value.replace(FUNCTIONS_RE, '$1')
|
||||
.replace(FUNCTIONS_RE, '$1')
|
||||
.replace(URL_RE, 'url');
|
||||
if (!VALUE_RE.test(valueWithoutFunctions)) {
|
||||
fail(
|
||||
`String value allows only ${VALUE_ALLOWED_CHARS}` +
|
||||
' and simple functions, got: ' + value);
|
||||
return SafeStyle.INNOCUOUS_STRING;
|
||||
} else if (COMMENT_RE.test(value)) {
|
||||
fail(`String value disallows comments, got: ${value}`);
|
||||
return SafeStyle.INNOCUOUS_STRING;
|
||||
} else if (!hasBalancedQuotes(value)) {
|
||||
fail(`String value requires balanced quotes, got: ${value}`);
|
||||
return SafeStyle.INNOCUOUS_STRING;
|
||||
} else if (!hasBalancedSquareBrackets(value)) {
|
||||
fail(
|
||||
'String value requires balanced square brackets and one' +
|
||||
' identifier per pair of brackets, got: ' + value);
|
||||
return SafeStyle.INNOCUOUS_STRING;
|
||||
}
|
||||
return sanitizeUrl(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that quotes (" and ') are properly balanced inside a string. Assumes
|
||||
* that neither escape (\) nor any other character that could result in
|
||||
* breaking out of a string parsing context are allowed;
|
||||
* see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
|
||||
* @param {string} value Untrusted CSS property value.
|
||||
* @return {boolean} True if property value is safe with respect to quote
|
||||
* balancedness.
|
||||
*/
|
||||
function hasBalancedQuotes(value) {
|
||||
'use strict';
|
||||
let outsideSingle = true;
|
||||
let outsideDouble = true;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const c = value.charAt(i);
|
||||
if (c == '\'' && outsideDouble) {
|
||||
outsideSingle = !outsideSingle;
|
||||
} else if (c == '"' && outsideSingle) {
|
||||
outsideDouble = !outsideDouble;
|
||||
}
|
||||
}
|
||||
return outsideSingle && outsideDouble;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that square brackets ([ and ]) are properly balanced inside a string,
|
||||
* and that the content in the square brackets is one ident-token;
|
||||
* see https://www.w3.org/TR/css-syntax-3/#ident-token-diagram.
|
||||
* For practicality, and in line with other restrictions posed on SafeStyle
|
||||
* strings, we restrict the character set allowable in the ident-token to
|
||||
* [-_a-zA-Z0-9].
|
||||
* @param {string} value Untrusted CSS property value.
|
||||
* @return {boolean} True if property value is safe with respect to square
|
||||
* bracket balancedness.
|
||||
*/
|
||||
function hasBalancedSquareBrackets(value) {
|
||||
'use strict';
|
||||
let outside = true;
|
||||
const tokenRe = /^[-_a-zA-Z0-9]$/;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const c = value.charAt(i);
|
||||
if (c == ']') {
|
||||
if (outside) return false; // Unbalanced ].
|
||||
outside = true;
|
||||
} else if (c == '[') {
|
||||
if (!outside) return false; // No nesting.
|
||||
outside = false;
|
||||
} else if (!outside && !tokenRe.test(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return outside;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Characters allowed in VALUE_RE.
|
||||
* @type {string}
|
||||
*/
|
||||
const VALUE_ALLOWED_CHARS = '[-,."\'%_!# a-zA-Z0-9\\[\\]]';
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for safe values.
|
||||
* Quotes (" and ') are allowed, but a check must be done elsewhere to ensure
|
||||
* they're balanced.
|
||||
* Square brackets ([ and ]) are allowed, but a check must be done elsewhere
|
||||
* to ensure they're balanced. The content inside a pair of square brackets must
|
||||
* be one alphanumeric identifier.
|
||||
* ',' allows multiple values to be assigned to the same property
|
||||
* (e.g. background-attachment or font-family) and hence could allow
|
||||
* multiple values to get injected, but that should pose no risk of XSS.
|
||||
* The expression checks only for XSS safety, not for CSS validity.
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
const VALUE_RE = new RegExp(`^${VALUE_ALLOWED_CHARS}+\$`);
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for url(). We support URLs allowed by
|
||||
* https://www.w3.org/TR/css-syntax-3/#url-token-diagram without using escape
|
||||
* sequences. Use percent-encoding if you need to use special characters like
|
||||
* backslash.
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
const URL_RE = new RegExp(
|
||||
'\\b(url\\([ \t\n]*)(' +
|
||||
'\'[ -&(-\\[\\]-~]*\'' + // Printable characters except ' and \.
|
||||
'|"[ !#-\\[\\]-~]*"' + // Printable characters except " and \.
|
||||
'|[!#-&*-\\[\\]-~]*' + // Printable characters except [ "'()\\].
|
||||
')([ \t\n]*\\))',
|
||||
'g');
|
||||
|
||||
/**
|
||||
* Names of functions allowed in FUNCTIONS_RE.
|
||||
* @const {!Array<string>}
|
||||
*/
|
||||
const ALLOWED_FUNCTIONS = [
|
||||
'calc',
|
||||
'cubic-bezier',
|
||||
'fit-content',
|
||||
'hsl',
|
||||
'hsla',
|
||||
'linear-gradient',
|
||||
'matrix',
|
||||
'minmax',
|
||||
'repeat',
|
||||
'rgb',
|
||||
'rgba',
|
||||
'(rotate|scale|translate)(X|Y|Z|3d)?',
|
||||
'var',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for simple functions.
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
const FUNCTIONS_RE = new RegExp(
|
||||
'\\b(' + ALLOWED_FUNCTIONS.join('|') + ')' +
|
||||
'\\([-+*/0-9a-z.%#\\[\\], ]+\\)',
|
||||
'g');
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for comments. These are disallowed in CSS property values.
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
const COMMENT_RE = /\/\*/;
|
||||
|
||||
|
||||
/**
|
||||
* Sanitize URLs inside url().
|
||||
* NOTE: We could also consider using CSS.escape once that's available in the
|
||||
* browsers. However, loosely matching URL e.g. with url\(.*\) and then escaping
|
||||
* the contents would result in a slightly different language than CSS leading
|
||||
* to confusion of users. E.g. url(")") is valid in CSS but it would be invalid
|
||||
* as seen by our parser. On the other hand, url(\) is invalid in CSS but our
|
||||
* parser would be fine with it.
|
||||
* @param {string} value Untrusted CSS property value.
|
||||
* @return {string}
|
||||
*/
|
||||
function sanitizeUrl(value) {
|
||||
'use strict';
|
||||
return value.replace(URL_RE, (match, before, url, after) => {
|
||||
'use strict';
|
||||
let quote = '';
|
||||
url = url.replace(/^(['"])(.*)\1$/, (match, start, inside) => {
|
||||
'use strict';
|
||||
quote = start;
|
||||
return inside;
|
||||
});
|
||||
const sanitized = SafeUrl.sanitize(url).getTypedStringValue();
|
||||
return before + quote + sanitized + quote + after;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
exports = SafeStyle;
|
||||
Executable
+297
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The SafeStyleSheet type and its builders.
|
||||
*
|
||||
* TODO(xtof): Link to document stating type contract.
|
||||
*/
|
||||
|
||||
goog.module('goog.html.SafeStyleSheet');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const Const = goog.require('goog.string.Const');
|
||||
const SafeStyle = goog.require('goog.html.SafeStyle');
|
||||
const TypedString = goog.require('goog.string.TypedString');
|
||||
const googObject = goog.require('goog.object');
|
||||
const {assert, fail} = goog.require('goog.asserts');
|
||||
const {contains} = goog.require('goog.string.internal');
|
||||
|
||||
/**
|
||||
* Token used to ensure that object is created only from this file. No code
|
||||
* outside of this file can access this token.
|
||||
* @const {!Object}
|
||||
*/
|
||||
const CONSTRUCTOR_TOKEN_PRIVATE = {};
|
||||
|
||||
/**
|
||||
* A string-like object which represents a CSS style sheet and that carries the
|
||||
* security type contract that its value, as a string, will not cause untrusted
|
||||
* script execution (XSS) when evaluated as CSS in a browser.
|
||||
*
|
||||
* Instances of this type must be created via the factory method
|
||||
* `SafeStyleSheet.fromConstant` and not by invoking its constructor. The
|
||||
* constructor intentionally takes an extra parameter that cannot be constructed
|
||||
* outside of this file and the type is immutable; hence only a default instance
|
||||
* corresponding to the empty string can be obtained via constructor invocation.
|
||||
*
|
||||
* A SafeStyleSheet's string representation can safely be interpolated as the
|
||||
* content of a style element within HTML. The SafeStyleSheet string should
|
||||
* not be escaped before interpolation.
|
||||
*
|
||||
* Values of this type must be composable, i.e. for any two values
|
||||
* `styleSheet1` and `styleSheet2` of this type,
|
||||
* `SafeStyleSheet.unwrap(styleSheet1) + SafeStyleSheet.unwrap(styleSheet2)`
|
||||
* must itself be a value that satisfies the SafeStyleSheet type constraint.
|
||||
* This requirement implies that for any value `styleSheet` of this type,
|
||||
* `SafeStyleSheet.unwrap(styleSheet1)` must end in
|
||||
* "beginning of rule" context.
|
||||
*
|
||||
* A SafeStyleSheet can be constructed via security-reviewed unchecked
|
||||
* conversions. In this case producers of SafeStyleSheet must ensure themselves
|
||||
* that the SafeStyleSheet does not contain unsafe script. Note in particular
|
||||
* that `<` is dangerous, even when inside CSS strings, and so should
|
||||
* always be forbidden or CSS-escaped in user controlled input. For example, if
|
||||
* `</style><script>evil</script>"` were interpolated
|
||||
* inside a CSS string, it would break out of the context of the original
|
||||
* style element and `evil` would execute. Also note that within an HTML
|
||||
* style (raw text) element, HTML character references, such as
|
||||
* `&lt;`, are not allowed. See
|
||||
* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
|
||||
* (similar considerations apply to the style element).
|
||||
*
|
||||
* @see SafeStyleSheet#fromConstant
|
||||
* @final
|
||||
* @implements {TypedString}
|
||||
*/
|
||||
class SafeStyleSheet {
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {!Object} token package-internal implementation detail.
|
||||
*/
|
||||
constructor(value, token) {
|
||||
/**
|
||||
* The contained value of this SafeStyleSheet. The field has a purposely
|
||||
* ugly name to make (non-compiled) code that attempts to directly access
|
||||
* this field stand out.
|
||||
* @private {string}
|
||||
*/
|
||||
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ =
|
||||
(token === CONSTRUCTOR_TOKEN_PRIVATE) ? value : '';
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
this.implementsGoogStringTypedString = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a style sheet consisting of one selector and one style definition.
|
||||
* Use {@link SafeStyleSheet.concat} to create longer style sheets.
|
||||
* This function doesn't support @import, @media and similar constructs.
|
||||
* @param {string} selector CSS selector, e.g. '#id' or 'tag .class, #id'. We
|
||||
* support CSS3 selectors: https://w3.org/TR/css3-selectors/#selectors.
|
||||
* @param {!SafeStyle.PropertyMap|!SafeStyle} style Style
|
||||
* definition associated with the selector.
|
||||
* @return {!SafeStyleSheet}
|
||||
* @throws {!Error} If invalid selector is provided.
|
||||
*/
|
||||
static createRule(selector, style) {
|
||||
if (contains(selector, '<')) {
|
||||
throw new Error(`Selector does not allow '<', got: ${selector}`);
|
||||
}
|
||||
|
||||
// Remove strings.
|
||||
const selectorToCheck =
|
||||
selector.replace(/('|")((?!\1)[^\r\n\f\\]|\\[\s\S])*\1/g, '');
|
||||
|
||||
// Check characters allowed in CSS3 selectors.
|
||||
if (!/^[-_a-zA-Z0-9#.:* ,>+~[\]()=^$|]+$/.test(selectorToCheck)) {
|
||||
throw new Error(
|
||||
'Selector allows only [-_a-zA-Z0-9#.:* ,>+~[\\]()=^$|] and ' +
|
||||
'strings, got: ' + selector);
|
||||
}
|
||||
|
||||
// Check balanced () and [].
|
||||
if (!SafeStyleSheet.hasBalancedBrackets_(selectorToCheck)) {
|
||||
throw new Error(
|
||||
'() and [] in selector must be balanced, got: ' + selector);
|
||||
}
|
||||
|
||||
if (!(style instanceof SafeStyle)) {
|
||||
style = SafeStyle.create(style);
|
||||
}
|
||||
const styleSheet =
|
||||
`${selector}{` + SafeStyle.unwrap(style).replace(/</g, '\\3C ') + '}';
|
||||
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
|
||||
styleSheet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string has balanced () and [] brackets.
|
||||
* @param {string} s String to check.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
static hasBalancedBrackets_(s) {
|
||||
const brackets = {'(': ')', '[': ']'};
|
||||
const expectedBrackets = [];
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
const ch = s[i];
|
||||
if (brackets[ch]) {
|
||||
expectedBrackets.push(brackets[ch]);
|
||||
} else if (googObject.contains(brackets, ch)) {
|
||||
if (expectedBrackets.pop() != ch) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return expectedBrackets.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SafeStyleSheet object by concatenating values.
|
||||
* @param {...(!SafeStyleSheet|!Array<!SafeStyleSheet>)}
|
||||
* var_args Values to concatenate.
|
||||
* @return {!SafeStyleSheet}
|
||||
*/
|
||||
static concat(var_args) {
|
||||
let result = '';
|
||||
|
||||
/**
|
||||
* @param {!SafeStyleSheet|!Array<!SafeStyleSheet>}
|
||||
* argument
|
||||
*/
|
||||
const addArgument = argument => {
|
||||
if (Array.isArray(argument)) {
|
||||
argument.forEach(addArgument);
|
||||
} else {
|
||||
result += SafeStyleSheet.unwrap(argument);
|
||||
}
|
||||
};
|
||||
|
||||
Array.prototype.forEach.call(arguments, addArgument);
|
||||
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
|
||||
result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SafeStyleSheet object from a compile-time constant string.
|
||||
*
|
||||
* `styleSheet` must not have any < characters in it, so that
|
||||
* the syntactic structure of the surrounding HTML is not affected.
|
||||
*
|
||||
* @param {!Const} styleSheet A compile-time-constant string from
|
||||
* which to create a SafeStyleSheet.
|
||||
* @return {!SafeStyleSheet} A SafeStyleSheet object initialized to
|
||||
* `styleSheet`.
|
||||
*/
|
||||
static fromConstant(styleSheet) {
|
||||
const styleSheetString = Const.unwrap(styleSheet);
|
||||
if (styleSheetString.length === 0) {
|
||||
return SafeStyleSheet.EMPTY;
|
||||
}
|
||||
// > is a valid character in CSS selectors and there's no strict need to
|
||||
// block it if we already block <.
|
||||
assert(
|
||||
!contains(styleSheetString, '<'),
|
||||
`Forbidden '<' character in style sheet string: ${styleSheetString}`);
|
||||
return SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(
|
||||
styleSheetString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this SafeStyleSheet's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security relevant that an object's type is
|
||||
* indeed `SafeStyleSheet`, use `SafeStyleSheet.unwrap`
|
||||
* instead of this method. If in doubt, assume that it's security relevant. In
|
||||
* particular, note that goog.html functions which return a goog.html type do
|
||||
* not guarantee the returned instance is of the right type. For example:
|
||||
*
|
||||
* <pre>
|
||||
* var fakeSafeHtml = new String('fake');
|
||||
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
|
||||
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
|
||||
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
|
||||
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
|
||||
* // instanceof goog.html.SafeHtml.
|
||||
* </pre>
|
||||
*
|
||||
* @see SafeStyleSheet#unwrap
|
||||
* @override
|
||||
*/
|
||||
getTypedStringValue() {
|
||||
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed a
|
||||
* SafeStyleSheet object, and returns its value.
|
||||
*
|
||||
* @param {!SafeStyleSheet} safeStyleSheet The object to extract from.
|
||||
* @return {string} The safeStyleSheet object's contained string, unless
|
||||
* the run-time type check fails. In that case, `unwrap` returns an
|
||||
* innocuous string, or, if assertions are enabled, throws
|
||||
* `asserts.AssertionError`.
|
||||
*/
|
||||
static unwrap(safeStyleSheet) {
|
||||
// Perform additional Run-time type-checking to ensure that
|
||||
// safeStyleSheet is indeed an instance of the expected type. This
|
||||
// provides some additional protection against security bugs due to
|
||||
// application code that disables type checks.
|
||||
// Specifically, the following checks are performed:
|
||||
// 1. The object is an instance of the expected type.
|
||||
// 2. The object is not an instance of a subclass.
|
||||
if (safeStyleSheet instanceof SafeStyleSheet &&
|
||||
safeStyleSheet.constructor === SafeStyleSheet) {
|
||||
return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
|
||||
} else {
|
||||
fail(
|
||||
'expected object of type SafeStyleSheet, got \'' + safeStyleSheet +
|
||||
'\' of type ' + goog.typeOf(safeStyleSheet));
|
||||
return 'type_error:SafeStyleSheet';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Package-internal utility method to create SafeStyleSheet instances.
|
||||
*
|
||||
* @param {string} styleSheet The string to initialize the SafeStyleSheet
|
||||
* object with.
|
||||
* @return {!SafeStyleSheet} The initialized SafeStyleSheet object.
|
||||
* @package
|
||||
*/
|
||||
static createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet) {
|
||||
return new SafeStyleSheet(styleSheet, CONSTRUCTOR_TOKEN_PRIVATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string-representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped in a SafeStyleSheet, use
|
||||
* `SafeStyleSheet.unwrap`.
|
||||
*
|
||||
* @return {string}
|
||||
* @see SafeStyleSheet#unwrap
|
||||
* @override
|
||||
*/
|
||||
SafeStyleSheet.prototype.toString = function() {
|
||||
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A SafeStyleSheet instance corresponding to the empty string.
|
||||
* @const {!SafeStyleSheet}
|
||||
*/
|
||||
SafeStyleSheet.EMPTY =
|
||||
SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
|
||||
|
||||
|
||||
exports = SafeStyleSheet;
|
||||
Executable
+807
@@ -0,0 +1,807 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The SafeUrl type and its builders.
|
||||
*
|
||||
* TODO(xtof): Link to document stating type contract.
|
||||
*/
|
||||
|
||||
goog.provide('goog.html.SafeUrl');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.fs.url');
|
||||
goog.require('goog.html.TrustedResourceUrl');
|
||||
goog.require('goog.i18n.bidi.Dir');
|
||||
goog.require('goog.i18n.bidi.DirectionalString');
|
||||
goog.require('goog.string.Const');
|
||||
goog.require('goog.string.TypedString');
|
||||
goog.require('goog.string.internal');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A string that is safe to use in URL context in DOM APIs and HTML documents.
|
||||
*
|
||||
* A SafeUrl is a string-like object that carries the security type contract
|
||||
* that its value as a string will not cause untrusted script execution
|
||||
* when evaluated as a hyperlink URL in a browser.
|
||||
*
|
||||
* Values of this type are guaranteed to be safe to use in URL/hyperlink
|
||||
* contexts, such as assignment to URL-valued DOM properties, in the sense that
|
||||
* the use will not result in a Cross-Site-Scripting vulnerability. Similarly,
|
||||
* SafeUrls can be interpolated into the URL context of an HTML template (e.g.,
|
||||
* inside a href attribute). However, appropriate HTML-escaping must still be
|
||||
* applied.
|
||||
*
|
||||
* Note that, as documented in `goog.html.SafeUrl.unwrap`, this type's
|
||||
* contract does not guarantee that instances are safe to interpolate into HTML
|
||||
* without appropriate escaping.
|
||||
*
|
||||
* Note also that this type's contract does not imply any guarantees regarding
|
||||
* the resource the URL refers to. In particular, SafeUrls are <b>not</b>
|
||||
* safe to use in a context where the referred-to resource is interpreted as
|
||||
* trusted code, e.g., as the src of a script tag.
|
||||
*
|
||||
* Instances of this type must be created via the factory methods
|
||||
* (`goog.html.SafeUrl.fromConstant`, `goog.html.SafeUrl.sanitize`),
|
||||
* etc and not by invoking its constructor. The constructor intentionally takes
|
||||
* an extra parameter that cannot be constructed outside of this file and the
|
||||
* type is immutable; hence only a default instance corresponding to the empty
|
||||
* string can be obtained via constructor invocation.
|
||||
*
|
||||
* @see goog.html.SafeUrl#fromConstant
|
||||
* @see goog.html.SafeUrl#from
|
||||
* @see goog.html.SafeUrl#sanitize
|
||||
* @final
|
||||
* @struct
|
||||
* @implements {goog.i18n.bidi.DirectionalString}
|
||||
* @implements {goog.string.TypedString}
|
||||
*/
|
||||
goog.html.SafeUrl = class {
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {!Object} token package-internal implementation detail.
|
||||
*/
|
||||
constructor(value, token) {
|
||||
/**
|
||||
* The contained value of this SafeUrl. The field has a purposely ugly
|
||||
* name to make (non-compiled) code that attempts to directly access this
|
||||
* field stand out.
|
||||
* @private {string}
|
||||
*/
|
||||
this.privateDoNotAccessOrElseSafeUrlWrappedValue_ =
|
||||
(token === goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_) ? value : '';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The innocuous string generated by goog.html.SafeUrl.sanitize when passed
|
||||
* an unsafe URL.
|
||||
*
|
||||
* about:invalid is registered in
|
||||
* http://www.w3.org/TR/css3-values/#about-invalid.
|
||||
* http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
|
||||
* contain a fragment, which is not to be considered when determining if an
|
||||
* about URL is well-known.
|
||||
*
|
||||
* Using about:invalid seems preferable to using a fixed data URL, since
|
||||
* browsers might choose to not report CSP violations on it, as legitimate
|
||||
* CSS function calls to attr() can result in this URL being produced. It is
|
||||
* also a standard URL which matches exactly the semantics we need:
|
||||
* "The about:invalid URI references a non-existent document with a generic
|
||||
* error condition. It can be used when a URI is necessary, but the default
|
||||
* value shouldn't be resolveable as any type of document".
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this SafeUrl's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security relevant that an object's type is
|
||||
* indeed `SafeUrl`, use `goog.html.SafeUrl.unwrap` instead of this
|
||||
* method. If in doubt, assume that it's security relevant. In particular, note
|
||||
* that goog.html functions which return a goog.html type do not guarantee that
|
||||
* the returned instance is of the right type.
|
||||
*
|
||||
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
|
||||
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
|
||||
* be appropriately escaped before embedding in a HTML document. Note that the
|
||||
* required escaping is context-sensitive (e.g. a different escaping is
|
||||
* required for embedding a URL in a style property within a style
|
||||
* attribute, as opposed to embedding in a href attribute).
|
||||
*
|
||||
* @see goog.html.SafeUrl#unwrap
|
||||
* @override
|
||||
*/
|
||||
goog.html.SafeUrl.prototype.getTypedStringValue = function() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseSafeUrlWrappedValue_.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const {boolean}
|
||||
*/
|
||||
goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this URLs directionality, which is always `LTR`.
|
||||
* @override
|
||||
* @return {!goog.i18n.bidi.Dir}
|
||||
*/
|
||||
goog.html.SafeUrl.prototype.getDirection = function() {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.Dir.LTR;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string-representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped in a SafeUrl, use
|
||||
* `goog.html.SafeUrl.unwrap`.
|
||||
*
|
||||
* @return {string}
|
||||
* @see goog.html.SafeUrl#unwrap
|
||||
* @override
|
||||
*/
|
||||
goog.html.SafeUrl.prototype.toString = function() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseSafeUrlWrappedValue_.toString();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed a SafeUrl
|
||||
* object, and returns its value.
|
||||
*
|
||||
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
|
||||
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
|
||||
* be appropriately escaped before embedding in a HTML document. Note that the
|
||||
* required escaping is context-sensitive (e.g. a different escaping is
|
||||
* required for embedding a URL in a style property within a style
|
||||
* attribute, as opposed to embedding in a href attribute).
|
||||
*
|
||||
* @param {!goog.html.SafeUrl} safeUrl The object to extract from.
|
||||
* @return {string} The SafeUrl object's contained string, unless the run-time
|
||||
* type check fails. In that case, `unwrap` returns an innocuous
|
||||
* string, or, if assertions are enabled, throws
|
||||
* `goog.asserts.AssertionError`.
|
||||
*/
|
||||
goog.html.SafeUrl.unwrap = function(safeUrl) {
|
||||
'use strict';
|
||||
// Perform additional Run-time type-checking to ensure that safeUrl is indeed
|
||||
// an instance of the expected type. This provides some additional protection
|
||||
// against security bugs due to application code that disables type checks.
|
||||
// Specifically, the following checks are performed:
|
||||
// 1. The object is an instance of the expected type.
|
||||
// 2. The object is not an instance of a subclass.
|
||||
if (safeUrl instanceof goog.html.SafeUrl &&
|
||||
safeUrl.constructor === goog.html.SafeUrl) {
|
||||
return safeUrl.privateDoNotAccessOrElseSafeUrlWrappedValue_;
|
||||
} else {
|
||||
goog.asserts.fail('expected object of type SafeUrl, got \'' +
|
||||
safeUrl + '\' of type ' + goog.typeOf(safeUrl));
|
||||
return 'type_error:SafeUrl';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl object from a compile-time constant string.
|
||||
*
|
||||
* Compile-time constant strings are inherently program-controlled and hence
|
||||
* trusted.
|
||||
*
|
||||
* @param {!goog.string.Const} url A compile-time-constant string from which to
|
||||
* create a SafeUrl.
|
||||
* @return {!goog.html.SafeUrl} A SafeUrl object initialized to `url`.
|
||||
*/
|
||||
goog.html.SafeUrl.fromConstant = function(url) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
goog.string.Const.unwrap(url));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A pattern that matches Blob or data types that can have SafeUrls created
|
||||
* from URL.createObjectURL(blob) or via a data: URI.
|
||||
*
|
||||
* This has some parameter support (most notably, we haven't implemented the
|
||||
* more complex parts like %-encoded characters or non-alphanumerical ones for
|
||||
* simplicity's sake). The specs are fairly complex, and they don't
|
||||
* always match Chrome's behavior: we settled on a subset where we're confident
|
||||
* all parties involved agree.
|
||||
*
|
||||
* The spec is available at https://mimesniff.spec.whatwg.org/ (and see
|
||||
* https://tools.ietf.org/html/rfc2397 for data: urls, which override some of
|
||||
* it).
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.html.SAFE_MIME_TYPE_PATTERN_ = new RegExp(
|
||||
// Note: Due to content-sniffing concerns, only add MIME types for
|
||||
// media formats.
|
||||
'^(?:audio/(?:3gpp2|3gpp|aac|L16|midi|mp3|mp4|mpeg|oga|ogg|opus|x-m4a|x-matroska|x-wav|wav|webm)|' +
|
||||
'font/\\w+|' +
|
||||
'image/(?:bmp|gif|jpeg|jpg|png|tiff|webp|x-icon)|' +
|
||||
'video/(?:mpeg|mp4|ogg|webm|quicktime|x-matroska))' +
|
||||
'(?:;\\w+=(?:\\w+|"[\\w;,= ]+"))*$', // MIME type parameters
|
||||
'i');
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType The MIME type to check if safe.
|
||||
* @return {boolean} True if the MIME type is safe and creating a Blob via
|
||||
* `SafeUrl.fromBlob()` with that type will not fail due to the type. False
|
||||
* otherwise.
|
||||
*/
|
||||
goog.html.SafeUrl.isSafeMimeType = function(mimeType) {
|
||||
'use strict';
|
||||
return goog.html.SAFE_MIME_TYPE_PATTERN_.test(mimeType);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a blob URL for the given `blob`.
|
||||
*
|
||||
* The blob URL is created with `URL.createObjectURL`. If the MIME type
|
||||
* for `blob` is not of a known safe audio, image or video MIME type,
|
||||
* then the SafeUrl will wrap {@link #INNOCUOUS_STRING}.
|
||||
*
|
||||
* Note: Call {@link revokeObjectUrl} on the URL after it's used
|
||||
* to prevent memory leaks.
|
||||
*
|
||||
* @see http://www.w3.org/TR/FileAPI/#url
|
||||
* @param {!Blob} blob
|
||||
* @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
|
||||
* as a SafeUrl.
|
||||
*/
|
||||
goog.html.SafeUrl.fromBlob = function(blob) {
|
||||
'use strict';
|
||||
var url = goog.html.SafeUrl.isSafeMimeType(blob.type) ?
|
||||
goog.fs.url.createObjectUrl(blob) :
|
||||
goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Revokes an object URL created for a safe URL created {@link fromBlob()}.
|
||||
* @param {!goog.html.SafeUrl} safeUrl SafeUrl wrapping a blob object.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.html.SafeUrl.revokeObjectUrl = function(safeUrl) {
|
||||
'use strict';
|
||||
var url = safeUrl.getTypedStringValue();
|
||||
if (url !== goog.html.SafeUrl.INNOCUOUS_STRING) {
|
||||
goog.fs.url.revokeObjectUrl(url);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a blob URL created for a MediaSource.
|
||||
* @param {!MediaSource} mediaSource
|
||||
* @return {!goog.html.SafeUrl} The blob URL.
|
||||
*/
|
||||
goog.html.SafeUrl.fromMediaSource = function(mediaSource) {
|
||||
'use strict';
|
||||
goog.asserts.assert(
|
||||
'MediaSource' in goog.global, 'No support for MediaSource');
|
||||
const url = mediaSource instanceof MediaSource ?
|
||||
goog.fs.url.createObjectUrl(mediaSource) :
|
||||
goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches a base-64 data URL, with the first match group being the MIME type.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.html.DATA_URL_PATTERN_ = /^data:(.*);base64,[a-z0-9+\/]+=*$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to create a SafeUrl wrapping a `data:` URL, after validating it
|
||||
* matches a known-safe media MIME type. If it doesn't match, return `null`.
|
||||
*
|
||||
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
|
||||
* media MIME types.
|
||||
* @return {?goog.html.SafeUrl} A matching safe URL, or `null` if it does not
|
||||
* pass.
|
||||
*/
|
||||
goog.html.SafeUrl.tryFromDataUrl = function(dataUrl) {
|
||||
'use strict';
|
||||
// For defensive purposes, in case users cast around the parameter type.
|
||||
dataUrl = String(dataUrl);
|
||||
// RFC4648 suggest to ignore CRLF in base64 encoding.
|
||||
// See https://tools.ietf.org/html/rfc4648.
|
||||
// Remove the CR (%0D) and LF (%0A) from the dataUrl.
|
||||
var filteredDataUrl = dataUrl.replace(/(%0A|%0D)/g, '');
|
||||
var match = filteredDataUrl.match(goog.html.DATA_URL_PATTERN_);
|
||||
// Note: The only risk of XSS here is if the `data:` URL results in a
|
||||
// same-origin document. In which case content-sniffing might cause the
|
||||
// browser to interpret the contents as html.
|
||||
// All modern browsers consider `data:` URL documents to have unique empty
|
||||
// origins. Only Firefox for versions prior to v57 behaves differently:
|
||||
// https://blog.mozilla.org/security/2017/10/04/treating-data-urls-unique-origins-firefox-57/
|
||||
// Older versions of IE don't understand `data:` urls, so it is not an issue.
|
||||
var valid = match && goog.html.SafeUrl.isSafeMimeType(match[1]);
|
||||
if (valid) {
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
filteredDataUrl);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a `data:` URL, after validating it matches a
|
||||
* known-safe media MIME type. If it doesn't match, return
|
||||
* `goog.html.SafeUrl.INNOCUOUS_URL`.
|
||||
*
|
||||
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
|
||||
* media MIME types.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or
|
||||
* `goog.html.SafeUrl.INNOCUOUS_URL` if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.tryFromDataUrl(dataUrl) ||
|
||||
goog.html.SafeUrl.INNOCUOUS_URL;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a tel: URL.
|
||||
*
|
||||
* @param {string} telUrl A tel URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromTelUrl = function(telUrl) {
|
||||
'use strict';
|
||||
// There's a risk that a tel: URL could immediately place a call once
|
||||
// clicked, without requiring user confirmation. For that reason it is
|
||||
// handled in this separate function.
|
||||
if (!goog.string.internal.caseInsensitiveStartsWith(telUrl, 'tel:')) {
|
||||
telUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
telUrl);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches a sip/sips URL. We only allow urls that consist of an email address.
|
||||
* The characters '?' and '#' are not allowed in the local part of the email
|
||||
* address.
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.html.SIP_URL_PATTERN_ = new RegExp(
|
||||
'^sip[s]?:[+a-z0-9_.!$%&\'*\\/=^`{|}~-]+@([a-z0-9-]+\\.)+[a-z0-9]{2,63}$',
|
||||
'i');
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a sip: URL. We only allow urls that consist of an
|
||||
* email address. The characters '?' and '#' are not allowed in the local part
|
||||
* of the email address.
|
||||
*
|
||||
* @param {string} sipUrl A sip URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromSipUrl = function(sipUrl) {
|
||||
'use strict';
|
||||
if (!goog.html.SIP_URL_PATTERN_.test(decodeURIComponent(sipUrl))) {
|
||||
sipUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
sipUrl);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a fb-messenger://share URL.
|
||||
*
|
||||
* @param {string} facebookMessengerUrl A facebook messenger URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromFacebookMessengerUrl = function(facebookMessengerUrl) {
|
||||
'use strict';
|
||||
if (!goog.string.internal.caseInsensitiveStartsWith(
|
||||
facebookMessengerUrl, 'fb-messenger://share')) {
|
||||
facebookMessengerUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
facebookMessengerUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a whatsapp://send URL.
|
||||
*
|
||||
* @param {string} whatsAppUrl A WhatsApp URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromWhatsAppUrl = function(whatsAppUrl) {
|
||||
'use strict';
|
||||
if (!goog.string.internal.caseInsensitiveStartsWith(
|
||||
whatsAppUrl, 'whatsapp://send')) {
|
||||
whatsAppUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
whatsAppUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a sms: URL.
|
||||
*
|
||||
* @param {string} smsUrl A sms URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromSmsUrl = function(smsUrl) {
|
||||
'use strict';
|
||||
if (!goog.string.internal.caseInsensitiveStartsWith(smsUrl, 'sms:') ||
|
||||
!goog.html.SafeUrl.isSmsUrlBodyValid_(smsUrl)) {
|
||||
smsUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
smsUrl);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Validates SMS URL `body` parameter, which is optional and should appear at
|
||||
* most once and should be percent-encoded if present. Rejects many malformed
|
||||
* bodies, but may spuriously reject some URLs and does not reject all malformed
|
||||
* sms: URLs.
|
||||
*
|
||||
* @param {string} smsUrl A sms URL.
|
||||
* @return {boolean} Whether SMS URL has a valid `body` parameter if it exists.
|
||||
* @private
|
||||
*/
|
||||
goog.html.SafeUrl.isSmsUrlBodyValid_ = function(smsUrl) {
|
||||
'use strict';
|
||||
var hash = smsUrl.indexOf('#');
|
||||
if (hash > 0) {
|
||||
smsUrl = smsUrl.substring(0, hash);
|
||||
}
|
||||
var bodyParams = smsUrl.match(/[?&]body=/gi);
|
||||
// "body" param is optional
|
||||
if (!bodyParams) {
|
||||
return true;
|
||||
}
|
||||
// "body" MUST only appear once
|
||||
if (bodyParams.length > 1) {
|
||||
return false;
|
||||
}
|
||||
// Get the encoded `body` parameter value.
|
||||
var bodyValue = smsUrl.match(/[?&]body=([^&]*)/)[1];
|
||||
if (!bodyValue) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
decodeURIComponent(bodyValue);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return /^(?:[a-z0-9\-_.~]|%[0-9a-f]{2})+$/i.test(bodyValue);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl wrapping a ssh: URL.
|
||||
*
|
||||
* @param {string} sshUrl A ssh URL.
|
||||
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
|
||||
* wrapped as a SafeUrl if it does not pass.
|
||||
*/
|
||||
goog.html.SafeUrl.fromSshUrl = function(sshUrl) {
|
||||
'use strict';
|
||||
if (!goog.string.internal.caseInsensitiveStartsWith(sshUrl, 'ssh://')) {
|
||||
sshUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
sshUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes a Chrome extension URL to SafeUrl, given a compile-time-constant
|
||||
* extension identifier. Can also be restricted to chrome extensions.
|
||||
*
|
||||
* @param {string} url The url to sanitize. Should start with the extension
|
||||
* scheme and the extension identifier.
|
||||
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
|
||||
* extension id to accept, as a compile-time constant or an array of those.
|
||||
*
|
||||
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
|
||||
* `INNOCUOUS_STRING` if it's not.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitizeChromeExtensionUrl = function(url, extensionId) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.sanitizeExtensionUrl_(
|
||||
/^chrome-extension:\/\/([^\/]+)\//, url, extensionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes a Firefox extension URL to SafeUrl, given a compile-time-constant
|
||||
* extension identifier. Can also be restricted to chrome extensions.
|
||||
*
|
||||
* @param {string} url The url to sanitize. Should start with the extension
|
||||
* scheme and the extension identifier.
|
||||
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
|
||||
* extension id to accept, as a compile-time constant or an array of those.
|
||||
*
|
||||
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
|
||||
* `INNOCUOUS_STRING` if it's not.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitizeFirefoxExtensionUrl = function(url, extensionId) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.sanitizeExtensionUrl_(
|
||||
/^moz-extension:\/\/([^\/]+)\//, url, extensionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes a Edge extension URL to SafeUrl, given a compile-time-constant
|
||||
* extension identifier. Can also be restricted to chrome extensions.
|
||||
*
|
||||
* @param {string} url The url to sanitize. Should start with the extension
|
||||
* scheme and the extension identifier.
|
||||
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
|
||||
* extension id to accept, as a compile-time constant or an array of those.
|
||||
*
|
||||
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
|
||||
* `INNOCUOUS_STRING` if it's not.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitizeEdgeExtensionUrl = function(url, extensionId) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.sanitizeExtensionUrl_(
|
||||
/^ms-browser-extension:\/\/([^\/]+)\//, url, extensionId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Private helper for converting extension URLs to SafeUrl, given the scheme for
|
||||
* that particular extension type. Use the sanitizeFirefoxExtensionUrl,
|
||||
* sanitizeChromeExtensionUrl or sanitizeEdgeExtensionUrl unless you're building
|
||||
* new helpers.
|
||||
*
|
||||
* @private
|
||||
* @param {!RegExp} scheme The scheme to accept as a RegExp extracting the
|
||||
* extension identifier.
|
||||
* @param {string} url The url to sanitize. Should start with the extension
|
||||
* scheme and the extension identifier.
|
||||
* @param {!goog.string.Const|!Array<!goog.string.Const>} extensionId The
|
||||
* extension id to accept, as a compile-time constant or an array of those.
|
||||
*
|
||||
* @return {!goog.html.SafeUrl} Either `url` if it's deemed safe, or
|
||||
* `INNOCUOUS_STRING` if it's not.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitizeExtensionUrl_ = function(scheme, url, extensionId) {
|
||||
'use strict';
|
||||
var matches = scheme.exec(url);
|
||||
if (!matches) {
|
||||
url = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
} else {
|
||||
var extractedExtensionId = matches[1];
|
||||
var acceptedExtensionIds;
|
||||
if (extensionId instanceof goog.string.Const) {
|
||||
acceptedExtensionIds = [goog.string.Const.unwrap(extensionId)];
|
||||
} else {
|
||||
acceptedExtensionIds = extensionId.map(function unwrap(x) {
|
||||
'use strict';
|
||||
return goog.string.Const.unwrap(x);
|
||||
});
|
||||
}
|
||||
if (acceptedExtensionIds.indexOf(extractedExtensionId) == -1) {
|
||||
url = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl from TrustedResourceUrl. This is safe because
|
||||
* TrustedResourceUrl is more tightly restricted than SafeUrl.
|
||||
*
|
||||
* @param {!goog.html.TrustedResourceUrl} trustedResourceUrl
|
||||
* @return {!goog.html.SafeUrl}
|
||||
*/
|
||||
goog.html.SafeUrl.fromTrustedResourceUrl = function(trustedResourceUrl) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
goog.html.TrustedResourceUrl.unwrap(trustedResourceUrl));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A pattern that recognizes a commonly useful subset of URLs that satisfy
|
||||
* the SafeUrl contract.
|
||||
*
|
||||
* This regular expression matches a subset of URLs that will not cause script
|
||||
* execution if used in URL context within a HTML document. Specifically, this
|
||||
* regular expression matches if (comment from here on and regex copied from
|
||||
* Soy's EscapingConventions):
|
||||
* (1) Either a protocol in a whitelist (http, https, mailto or ftp).
|
||||
* (2) or no protocol. A protocol must be followed by a colon. The below
|
||||
* allows that by allowing colons only after one of the characters [/?#].
|
||||
* A colon after a hash (#) must be in the fragment.
|
||||
* Otherwise, a colon after a (?) must be in a query.
|
||||
* Otherwise, a colon after a single solidus (/) must be in a path.
|
||||
* Otherwise, a colon after a double solidus (//) must be in the authority
|
||||
* (before port).
|
||||
*
|
||||
* @private
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
goog.html.SAFE_URL_PATTERN_ =
|
||||
/^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i;
|
||||
|
||||
/**
|
||||
* Public version of goog.html.SAFE_URL_PATTERN_. Updating
|
||||
* goog.html.SAFE_URL_PATTERN_ doesn't seem to be backward compatible.
|
||||
* Namespace is also changed to goog.html.SafeUrl so it can be imported using
|
||||
* goog.require('goog.dom.SafeUrl').
|
||||
*
|
||||
* TODO(bangert): Remove SAFE_URL_PATTERN_
|
||||
* @const {!RegExp}
|
||||
*/
|
||||
goog.html.SafeUrl.SAFE_URL_PATTERN = goog.html.SAFE_URL_PATTERN_;
|
||||
|
||||
/**
|
||||
* Attempts to create a SafeUrl object from `url`. The input string is validated
|
||||
* to match a pattern of commonly used safe URLs. If validation fails, `null` is
|
||||
* returned.
|
||||
*
|
||||
* `url` may be a URL with the `http:`, `https:`, `mailto:`, `ftp:` or `data`
|
||||
* scheme, or a relative URL (i.e., a URL without a scheme; specifically, a
|
||||
* scheme-relative, absolute-path-relative, or path-relative URL).
|
||||
*
|
||||
* @see http://url.spec.whatwg.org/#concept-relative-url
|
||||
* @param {string|!goog.string.TypedString} url The URL to validate.
|
||||
* @return {?goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl, or null
|
||||
* if validation fails.
|
||||
*/
|
||||
goog.html.SafeUrl.trySanitize = function(url) {
|
||||
'use strict';
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
return url;
|
||||
}
|
||||
if (typeof url == 'object' && url.implementsGoogStringTypedString) {
|
||||
url = /** @type {!goog.string.TypedString} */ (url).getTypedStringValue();
|
||||
} else {
|
||||
// For defensive purposes, in case users cast around the parameter type.
|
||||
url = String(url);
|
||||
}
|
||||
if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
|
||||
return goog.html.SafeUrl.tryFromDataUrl(url);
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl object from `url`. If `url` is a
|
||||
* `goog.html.SafeUrl` then it is simply returned. Otherwise the input string is
|
||||
* validated to match a pattern of commonly used safe URLs. If validation fails,
|
||||
* `goog.html.SafeUrl.INNOCUOUS_URL` is returned.
|
||||
*
|
||||
* `url` may be a URL with the `http:`, `https:`, `mailto:`, `ftp:` or `data`
|
||||
* scheme, or a relative URL (i.e., a URL without a scheme; specifically, a
|
||||
* scheme-relative, absolute-path-relative, or path-relative URL).
|
||||
*
|
||||
* @see http://url.spec.whatwg.org/#concept-relative-url
|
||||
* @param {string|!goog.string.TypedString} url The URL to validate.
|
||||
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitize = function(url) {
|
||||
'use strict';
|
||||
return goog.html.SafeUrl.trySanitize(url) || goog.html.SafeUrl.INNOCUOUS_URL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a SafeUrl object from `url`. If `url` is a
|
||||
* `goog.html.SafeUrl` then it is simply returned. Otherwise the input string is
|
||||
* validated to match a pattern of commonly used safe URLs.
|
||||
*
|
||||
* `url` may be a URL with the http, https, mailto or ftp scheme,
|
||||
* or a relative URL (i.e., a URL without a scheme; specifically, a
|
||||
* scheme-relative, absolute-path-relative, or path-relative URL).
|
||||
*
|
||||
* This function asserts (using goog.asserts) that the URL matches this pattern.
|
||||
* If it does not, in addition to failing the assert, an innocuous URL will be
|
||||
* returned.
|
||||
*
|
||||
* @see http://url.spec.whatwg.org/#concept-relative-url
|
||||
* @param {string|!goog.string.TypedString} url The URL to validate.
|
||||
* @param {boolean=} opt_allowDataUrl Whether to allow valid data: URLs.
|
||||
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
|
||||
*/
|
||||
goog.html.SafeUrl.sanitizeAssertUnchanged = function(url, opt_allowDataUrl) {
|
||||
'use strict';
|
||||
if (url instanceof goog.html.SafeUrl) {
|
||||
return url;
|
||||
} else if (typeof url == 'object' && url.implementsGoogStringTypedString) {
|
||||
url = /** @type {!goog.string.TypedString} */ (url).getTypedStringValue();
|
||||
} else {
|
||||
url = String(url);
|
||||
}
|
||||
if (opt_allowDataUrl && /^data:/i.test(url)) {
|
||||
var safeUrl = goog.html.SafeUrl.fromDataUrl(url);
|
||||
if (safeUrl.getTypedStringValue() == url) {
|
||||
return safeUrl;
|
||||
}
|
||||
}
|
||||
if (!goog.asserts.assert(
|
||||
goog.html.SAFE_URL_PATTERN_.test(url),
|
||||
'%s does not match the safe URL pattern', url)) {
|
||||
url = goog.html.SafeUrl.INNOCUOUS_STRING;
|
||||
}
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
/**
|
||||
* Token used to ensure that object is created only from this file. No code
|
||||
* outside of this file can access this token.
|
||||
* @private {!Object}
|
||||
* @const
|
||||
*/
|
||||
goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_ = {};
|
||||
|
||||
/**
|
||||
* Package-internal utility method to create SafeUrl instances.
|
||||
*
|
||||
* @param {string} url The string to initialize the SafeUrl object with.
|
||||
* @return {!goog.html.SafeUrl} The initialized SafeUrl object.
|
||||
* @package
|
||||
*/
|
||||
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
|
||||
url) {
|
||||
'use strict';
|
||||
return new goog.html.SafeUrl(
|
||||
url, goog.html.SafeUrl.CONSTRUCTOR_TOKEN_PRIVATE_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* `INNOCUOUS_STRING` wrapped in a `SafeUrl`.
|
||||
* @const {!goog.html.SafeUrl}
|
||||
*/
|
||||
goog.html.SafeUrl.INNOCUOUS_URL =
|
||||
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
goog.html.SafeUrl.INNOCUOUS_STRING);
|
||||
|
||||
|
||||
/**
|
||||
* A SafeUrl corresponding to the special about:blank url.
|
||||
* @const {!goog.html.SafeUrl}
|
||||
*/
|
||||
goog.html.SafeUrl.ABOUT_BLANK =
|
||||
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
|
||||
'about:blank');
|
||||
Executable
+524
@@ -0,0 +1,524 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The TrustedResourceUrl type and its builders.
|
||||
*
|
||||
* TODO(xtof): Link to document stating type contract.
|
||||
*/
|
||||
|
||||
goog.provide('goog.html.TrustedResourceUrl');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.fs.blob');
|
||||
goog.require('goog.fs.url');
|
||||
goog.require('goog.html.SafeScript');
|
||||
goog.require('goog.html.trustedtypes');
|
||||
goog.require('goog.i18n.bidi.Dir');
|
||||
goog.require('goog.i18n.bidi.DirectionalString');
|
||||
goog.require('goog.string.Const');
|
||||
goog.require('goog.string.TypedString');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A URL which is under application control and from which script, CSS, and
|
||||
* other resources that represent executable code, can be fetched.
|
||||
*
|
||||
* Given that the URL can only be constructed from strings under application
|
||||
* control and is used to load resources, bugs resulting in a malformed URL
|
||||
* should not have a security impact and are likely to be easily detectable
|
||||
* during testing. Given the wide number of non-RFC compliant URLs in use,
|
||||
* stricter validation could prevent some applications from being able to use
|
||||
* this type.
|
||||
*
|
||||
* Instances of this type must be created via the factory method,
|
||||
* (`fromConstant`, `fromConstants`, `format` or `formatWithParams`), and not by
|
||||
* invoking its constructor. The constructor intentionally takes an extra
|
||||
* parameter that cannot be constructed outside of this file and the type is
|
||||
* immutable; hence only a default instance corresponding to the empty string
|
||||
* can be obtained via constructor invocation.
|
||||
*
|
||||
* Creating TrustedResourceUrl objects HAS SIDE-EFFECTS due to calling
|
||||
* Trusted Types Web API.
|
||||
*
|
||||
* @see goog.html.TrustedResourceUrl#fromConstant
|
||||
* @final
|
||||
* @struct
|
||||
* @implements {goog.i18n.bidi.DirectionalString}
|
||||
* @implements {goog.string.TypedString}
|
||||
*/
|
||||
goog.html.TrustedResourceUrl = class {
|
||||
/**
|
||||
* @param {!TrustedScriptURL|string} value
|
||||
* @param {!Object} token package-internal implementation detail.
|
||||
*/
|
||||
constructor(value, token) {
|
||||
/**
|
||||
* The contained value of this TrustedResourceUrl. The field has a
|
||||
* purposely ugly name to make (non-compiled) code that attempts to directly
|
||||
* access this field stand out.
|
||||
* @const
|
||||
* @private {!TrustedScriptURL|string}
|
||||
*/
|
||||
this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ =
|
||||
(token === goog.html.TrustedResourceUrl.CONSTRUCTOR_TOKEN_PRIVATE_) ?
|
||||
value :
|
||||
'';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this TrustedResourceUrl's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security relevant that an object's type is
|
||||
* indeed `TrustedResourceUrl`, use
|
||||
* `goog.html.TrustedResourceUrl.unwrap` instead of this method. If in
|
||||
* doubt, assume that it's security relevant. In particular, note that
|
||||
* goog.html functions which return a goog.html type do not guarantee that
|
||||
* the returned instance is of the right type. For example:
|
||||
*
|
||||
* <pre>
|
||||
* var fakeSafeHtml = new String('fake');
|
||||
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
|
||||
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
|
||||
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
|
||||
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
|
||||
* // goog.html.SafeHtml.
|
||||
* </pre>
|
||||
*
|
||||
* @see goog.html.TrustedResourceUrl#unwrap
|
||||
* @override
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_
|
||||
.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
|
||||
true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this URLs directionality, which is always `LTR`.
|
||||
* @override
|
||||
* @return {!goog.i18n.bidi.Dir}
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.getDirection = function() {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.Dir.LTR;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new TrustedResourceUrl with params added to URL. Both search and
|
||||
* hash params can be specified.
|
||||
*
|
||||
* @param {string|?Object<string, *>|undefined} searchParams Search parameters
|
||||
* to add to URL. See goog.html.TrustedResourceUrl.stringifyParams_ for
|
||||
* exact format definition.
|
||||
* @param {(string|?Object<string, *>)=} opt_hashParams Hash parameters to add
|
||||
* to URL. See goog.html.TrustedResourceUrl.stringifyParams_ for exact
|
||||
* format definition.
|
||||
* @return {!goog.html.TrustedResourceUrl} New TrustedResourceUrl with params.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.cloneWithParams = function(
|
||||
searchParams, opt_hashParams) {
|
||||
'use strict';
|
||||
var url = goog.html.TrustedResourceUrl.unwrap(this);
|
||||
var parts = goog.html.TrustedResourceUrl.URL_PARAM_PARSER_.exec(url);
|
||||
var urlBase = parts[1];
|
||||
var urlSearch = parts[2] || '';
|
||||
var urlHash = parts[3] || '';
|
||||
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
|
||||
urlBase +
|
||||
goog.html.TrustedResourceUrl.stringifyParams_(
|
||||
'?', urlSearch, searchParams) +
|
||||
goog.html.TrustedResourceUrl.stringifyParams_(
|
||||
'#', urlHash, opt_hashParams));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string-representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped in a TrustedResourceUrl, use
|
||||
* `goog.html.TrustedResourceUrl.unwrap`.
|
||||
*
|
||||
* @return {string}
|
||||
* @see goog.html.TrustedResourceUrl#unwrap
|
||||
* @override
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.prototype.toString = function() {
|
||||
'use strict';
|
||||
return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed a
|
||||
* TrustedResourceUrl object, and returns its value.
|
||||
*
|
||||
* @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to
|
||||
* extract from.
|
||||
* @return {string} The trustedResourceUrl object's contained string, unless
|
||||
* the run-time type check fails. In that case, `unwrap` returns an
|
||||
* innocuous string, or, if assertions are enabled, throws
|
||||
* `goog.asserts.AssertionError`.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) {
|
||||
'use strict';
|
||||
return goog.html.TrustedResourceUrl.unwrapTrustedScriptURL(trustedResourceUrl)
|
||||
.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps value as TrustedScriptURL if supported or as a string if not.
|
||||
* @param {!goog.html.TrustedResourceUrl} trustedResourceUrl
|
||||
* @return {!TrustedScriptURL|string}
|
||||
* @see goog.html.TrustedResourceUrl.unwrap
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.unwrapTrustedScriptURL = function(
|
||||
trustedResourceUrl) {
|
||||
'use strict';
|
||||
// Perform additional Run-time type-checking to ensure that
|
||||
// trustedResourceUrl is indeed an instance of the expected type. This
|
||||
// provides some additional protection against security bugs due to
|
||||
// application code that disables type checks.
|
||||
// Specifically, the following checks are performed:
|
||||
// 1. The object is an instance of the expected type.
|
||||
// 2. The object is not an instance of a subclass.
|
||||
if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl &&
|
||||
trustedResourceUrl.constructor === goog.html.TrustedResourceUrl) {
|
||||
return trustedResourceUrl
|
||||
.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
|
||||
} else {
|
||||
goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' +
|
||||
trustedResourceUrl + '\' of type ' + goog.typeOf(trustedResourceUrl));
|
||||
return 'type_error:TrustedResourceUrl';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a TrustedResourceUrl from a format string and arguments.
|
||||
*
|
||||
* The arguments for interpolation into the format string map labels to values.
|
||||
* Values of type `goog.string.Const` are interpolated without modifcation.
|
||||
* Values of other types are cast to string and encoded with
|
||||
* encodeURIComponent.
|
||||
*
|
||||
* `%{<label>}` markers are used in the format string to indicate locations
|
||||
* to be interpolated with the valued mapped to the given label. `<label>`
|
||||
* must contain only alphanumeric and `_` characters.
|
||||
*
|
||||
* The format string must match goog.html.TrustedResourceUrl.BASE_URL_.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* var url = goog.html.TrustedResourceUrl.format(goog.string.Const.from(
|
||||
* 'https://www.google.com/search?q=%{query}'), {'query': searchTerm});
|
||||
*
|
||||
* var url = goog.html.TrustedResourceUrl.format(goog.string.Const.from(
|
||||
* '//www.youtube.com/v/%{videoId}?hl=en&fs=1%{autoplay}'), {
|
||||
* 'videoId': videoId,
|
||||
* 'autoplay': opt_autoplay ?
|
||||
* goog.string.Const.from('&autoplay=1') : goog.string.Const.EMPTY
|
||||
* });
|
||||
*
|
||||
* While this function can be used to create a TrustedResourceUrl from only
|
||||
* constants, fromConstant() and fromConstants() are generally preferable for
|
||||
* that purpose.
|
||||
*
|
||||
* @param {!goog.string.Const} format The format string.
|
||||
* @param {!Object<string, (string|number|!goog.string.Const)>} args Mapping
|
||||
* of labels to values to be interpolated into the format string.
|
||||
* goog.string.Const values are interpolated without encoding.
|
||||
* @return {!goog.html.TrustedResourceUrl}
|
||||
* @throws {!Error} On an invalid format string or if a label used in the
|
||||
* the format string is not present in args.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.format = function(format, args) {
|
||||
'use strict';
|
||||
var formatStr = goog.string.Const.unwrap(format);
|
||||
if (!goog.html.TrustedResourceUrl.BASE_URL_.test(formatStr)) {
|
||||
throw new Error('Invalid TrustedResourceUrl format: ' + formatStr);
|
||||
}
|
||||
var result = formatStr.replace(
|
||||
goog.html.TrustedResourceUrl.FORMAT_MARKER_, function(match, id) {
|
||||
'use strict';
|
||||
if (!Object.prototype.hasOwnProperty.call(args, id)) {
|
||||
throw new Error(
|
||||
'Found marker, "' + id + '", in format string, "' + formatStr +
|
||||
'", but no valid label mapping found ' +
|
||||
'in args: ' + JSON.stringify(args));
|
||||
}
|
||||
var arg = args[id];
|
||||
if (arg instanceof goog.string.Const) {
|
||||
return goog.string.Const.unwrap(arg);
|
||||
} else {
|
||||
return encodeURIComponent(String(arg));
|
||||
}
|
||||
});
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(result);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private @const {!RegExp}
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.FORMAT_MARKER_ = /%{(\w+)}/g;
|
||||
|
||||
|
||||
/**
|
||||
* The URL must be absolute, scheme-relative or path-absolute. So it must
|
||||
* start with:
|
||||
* - https:// followed by allowed origin characters.
|
||||
* - // followed by allowed origin characters.
|
||||
* - Any absolute or relative path.
|
||||
*
|
||||
* Based on
|
||||
* https://url.spec.whatwg.org/commit-snapshots/56b74ce7cca8883eab62e9a12666e2fac665d03d/#url-parsing
|
||||
* an initial / which is not followed by another / or \ will end up in the "path
|
||||
* state" and from there it can only go to "fragment state" and "query state".
|
||||
*
|
||||
* We don't enforce a well-formed domain name. So '.' or '1.2' are valid.
|
||||
* That's ok because the origin comes from a compile-time constant.
|
||||
*
|
||||
* A regular expression is used instead of goog.uri for several reasons:
|
||||
* - Strictness. E.g. we don't want any userinfo component and we don't
|
||||
* want '/./, nor \' in the first path component.
|
||||
* - Small trusted base. goog.uri is generic and might need to change,
|
||||
* reasoning about all the ways it can parse a URL now and in the future
|
||||
* is error-prone.
|
||||
* - Code size. We expect many calls to .format(), many of which might
|
||||
* not be using goog.uri.
|
||||
* - Simplicity. Using goog.uri would likely not result in simpler nor shorter
|
||||
* code.
|
||||
* @private @const {!RegExp}
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.BASE_URL_ = new RegExp(
|
||||
'^((https:)?//[0-9a-z.:[\\]-]+/' // Origin.
|
||||
+ '|/[^/\\\\]' // Absolute path.
|
||||
+ '|[^:/\\\\%]+/' // Relative path.
|
||||
+ '|[^:/\\\\%]*[?#]' // Query string or fragment.
|
||||
+ '|about:blank#' // about:blank with fragment.
|
||||
+ ')',
|
||||
'i');
|
||||
|
||||
/**
|
||||
* RegExp for splitting a URL into the base, search field, and hash field.
|
||||
*
|
||||
* @private @const {!RegExp}
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.URL_PARAM_PARSER_ =
|
||||
/^([^?#]*)(\?[^#]*)?(#[\s\S]*)?/;
|
||||
|
||||
|
||||
/**
|
||||
* Formats the URL same as TrustedResourceUrl.format and then adds extra URL
|
||||
* parameters.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* // Creates '//www.youtube.com/v/abc?autoplay=1' for videoId='abc' and
|
||||
* // opt_autoplay=1. Creates '//www.youtube.com/v/abc' for videoId='abc'
|
||||
* // and opt_autoplay=undefined.
|
||||
* var url = goog.html.TrustedResourceUrl.formatWithParams(
|
||||
* goog.string.Const.from('//www.youtube.com/v/%{videoId}'),
|
||||
* {'videoId': videoId},
|
||||
* {'autoplay': opt_autoplay});
|
||||
*
|
||||
* @param {!goog.string.Const} format The format string.
|
||||
* @param {!Object<string, (string|number|!goog.string.Const)>} args Mapping
|
||||
* of labels to values to be interpolated into the format string.
|
||||
* goog.string.Const values are interpolated without encoding.
|
||||
* @param {string|?Object<string, *>|undefined} searchParams Parameters to add
|
||||
* to URL. See goog.html.TrustedResourceUrl.stringifyParams_ for exact
|
||||
* format definition.
|
||||
* @param {(string|?Object<string, *>)=} opt_hashParams Hash parameters to add
|
||||
* to URL. See goog.html.TrustedResourceUrl.stringifyParams_ for exact
|
||||
* format definition.
|
||||
* @return {!goog.html.TrustedResourceUrl}
|
||||
* @throws {!Error} On an invalid format string or if a label used in the
|
||||
* the format string is not present in args.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.formatWithParams = function(
|
||||
format, args, searchParams, opt_hashParams) {
|
||||
'use strict';
|
||||
var url = goog.html.TrustedResourceUrl.format(format, args);
|
||||
return url.cloneWithParams(searchParams, opt_hashParams);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a TrustedResourceUrl object from a compile-time constant string.
|
||||
*
|
||||
* Compile-time constant strings are inherently program-controlled and hence
|
||||
* trusted.
|
||||
*
|
||||
* @param {!goog.string.Const} url A compile-time-constant string from which to
|
||||
* create a TrustedResourceUrl.
|
||||
* @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
|
||||
* initialized to `url`.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.fromConstant = function(url) {
|
||||
'use strict';
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
|
||||
goog.string.Const.unwrap(url));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a TrustedResourceUrl object from a compile-time constant strings.
|
||||
*
|
||||
* Compile-time constant strings are inherently program-controlled and hence
|
||||
* trusted.
|
||||
*
|
||||
* @param {!Array<!goog.string.Const>} parts Compile-time-constant strings from
|
||||
* which to create a TrustedResourceUrl.
|
||||
* @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
|
||||
* initialized to concatenation of `parts`.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.fromConstants = function(parts) {
|
||||
'use strict';
|
||||
var unwrapped = '';
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
unwrapped += goog.string.Const.unwrap(parts[i]);
|
||||
}
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(unwrapped);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a TrustedResourceUrl object by generating a Blob from a SafeScript
|
||||
* object and then calling createObjectURL with that blob.
|
||||
*
|
||||
* SafeScript objects are trusted to contain executable JavaScript code.
|
||||
*
|
||||
* Caller must call goog.fs.url.revokeObjectUrl() on the unwrapped url to
|
||||
* release the underlying blob.
|
||||
*
|
||||
* Throws if browser doesn't support blob construction.
|
||||
*
|
||||
* @param {!goog.html.SafeScript} safeScript A script from which to create a
|
||||
* TrustedResourceUrl.
|
||||
* @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
|
||||
* initialized to a new blob URL.
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.fromSafeScript = function(safeScript) {
|
||||
'use strict';
|
||||
var blob = goog.fs.blob.getBlobWithProperties(
|
||||
[goog.html.SafeScript.unwrap(safeScript)], 'text/javascript');
|
||||
var url = goog.fs.url.createObjectUrl(blob);
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Token used to ensure that object is created only from this file. No code
|
||||
* outside of this file can access this token.
|
||||
* @private {!Object}
|
||||
* @const
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.CONSTRUCTOR_TOKEN_PRIVATE_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Package-internal utility method to create TrustedResourceUrl instances.
|
||||
*
|
||||
* @param {string} url The string to initialize the TrustedResourceUrl object
|
||||
* with.
|
||||
* @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl
|
||||
* object.
|
||||
* @package
|
||||
*/
|
||||
goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) {
|
||||
'use strict';
|
||||
const policy = goog.html.trustedtypes.getPolicyPrivateDoNotAccessOrElse();
|
||||
var value = policy ? policy.createScriptURL(url) : url;
|
||||
return new goog.html.TrustedResourceUrl(
|
||||
value, goog.html.TrustedResourceUrl.CONSTRUCTOR_TOKEN_PRIVATE_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stringifies the passed params to be used as either a search or hash field of
|
||||
* a URL.
|
||||
*
|
||||
* @param {string} prefix The prefix character for the given field ('?' or '#').
|
||||
* @param {string} currentString The existing field value (including the prefix
|
||||
* character, if the field is present).
|
||||
* @param {string|?Object<string, *>|undefined} params The params to set or
|
||||
* append to the field.
|
||||
* - If `undefined` or `null`, the field remains unchanged.
|
||||
* - If a string, then the string will be escaped and the field will be
|
||||
* overwritten with that value.
|
||||
* - If an Object, that object is treated as a set of key-value pairs to be
|
||||
* appended to the current field. Note that JavaScript doesn't guarantee the
|
||||
* order of values in an object which might result in non-deterministic order
|
||||
* of the parameters. However, browsers currently preserve the order. The
|
||||
* rules for each entry:
|
||||
* - If an array, it will be processed as if each entry were an additional
|
||||
* parameter with exactly the same key, following the same logic below.
|
||||
* - If `undefined` or `null`, it will be skipped.
|
||||
* - Otherwise, it will be turned into a string, escaped, and appended.
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
goog.html.TrustedResourceUrl.stringifyParams_ = function(
|
||||
prefix, currentString, params) {
|
||||
'use strict';
|
||||
if (params == null) {
|
||||
// Do not modify the field.
|
||||
return currentString;
|
||||
}
|
||||
if (typeof params === 'string') {
|
||||
// Set field to the passed string.
|
||||
return params ? prefix + encodeURIComponent(params) : '';
|
||||
}
|
||||
// Add on parameters to field from key-value object.
|
||||
for (var key in params) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty#Using_hasOwnProperty_as_a_property_name
|
||||
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
var value = params[key];
|
||||
var outputValues = Array.isArray(value) ? value : [value];
|
||||
for (var i = 0; i < outputValues.length; i++) {
|
||||
var outputValue = outputValues[i];
|
||||
if (outputValue != null) {
|
||||
if (!currentString) {
|
||||
currentString = prefix;
|
||||
}
|
||||
currentString += (currentString.length > prefix.length ? '&' : '') +
|
||||
encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(String(outputValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentString;
|
||||
};
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Policy to convert strings to Trusted Types. See
|
||||
* https://github.com/WICG/trusted-types for details.
|
||||
*/
|
||||
|
||||
goog.provide('goog.html.trustedtypes');
|
||||
|
||||
|
||||
/**
|
||||
* Cached result of goog.createTrustedTypesPolicy.
|
||||
* @type {?TrustedTypePolicy|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.html.trustedtypes.cachedPolicy_;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a (singleton) Trusted Type Policy for Safe HTML Types.
|
||||
* @return {?TrustedTypePolicy}
|
||||
* @package
|
||||
*/
|
||||
goog.html.trustedtypes.getPolicyPrivateDoNotAccessOrElse = function() {
|
||||
'use strict';
|
||||
if (!goog.TRUSTED_TYPES_POLICY_NAME) {
|
||||
// Binary not configured for Trusted Types.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (goog.html.trustedtypes.cachedPolicy_ === undefined) {
|
||||
goog.html.trustedtypes.cachedPolicy_ =
|
||||
goog.createTrustedTypesPolicy(goog.TRUSTED_TYPES_POLICY_NAME + '#html');
|
||||
}
|
||||
|
||||
return goog.html.trustedtypes.cachedPolicy_;
|
||||
};
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Unchecked conversions to create values of goog.html types from
|
||||
* plain strings. Use of these functions could potentially result in instances
|
||||
* of goog.html types that violate their type contracts, and hence result in
|
||||
* security vulnerabilties.
|
||||
*
|
||||
* Therefore, all uses of the methods herein must be carefully security
|
||||
* reviewed. Avoid use of the methods in this file whenever possible; instead
|
||||
* prefer to create instances of goog.html types using inherently safe builders
|
||||
* or template systems.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.html.uncheckedconversions');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.html.SafeScript');
|
||||
goog.require('goog.html.SafeStyle');
|
||||
goog.require('goog.html.SafeStyleSheet');
|
||||
goog.require('goog.html.SafeUrl');
|
||||
goog.require('goog.html.TrustedResourceUrl');
|
||||
goog.require('goog.string.Const');
|
||||
goog.require('goog.string.internal');
|
||||
goog.requireType('goog.i18n.bidi.Dir');
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to SafeHtml from a plain string that is
|
||||
* known to satisfy the SafeHtml type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `html` satisfies the SafeHtml type contract in all
|
||||
* possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} html A string that is claimed to adhere to the SafeHtml
|
||||
* contract.
|
||||
* @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
|
||||
* SafeHtml to be constructed. A null or undefined value signifies an
|
||||
* unknown directionality.
|
||||
* @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
|
||||
* object.
|
||||
*/
|
||||
goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
|
||||
function(justification, html, opt_dir) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
|
||||
html, opt_dir || null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to SafeScript from a plain string that is
|
||||
* known to satisfy the SafeScript type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `script` satisfies the SafeScript type contract in
|
||||
* all possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} script The string to wrap as a SafeScript.
|
||||
* @return {!goog.html.SafeScript} The value of `script`, wrapped in a
|
||||
* SafeScript object.
|
||||
*/
|
||||
goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
|
||||
function(justification, script) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
|
||||
script);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to SafeStyle from a plain string that is
|
||||
* known to satisfy the SafeStyle type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `style` satisfies the SafeStyle type contract in all
|
||||
* possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} style The string to wrap as a SafeStyle.
|
||||
* @return {!goog.html.SafeStyle} The value of `style`, wrapped in a
|
||||
* SafeStyle object.
|
||||
*/
|
||||
goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
|
||||
function(justification, style) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
|
||||
style);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to SafeStyleSheet from a plain string
|
||||
* that is known to satisfy the SafeStyleSheet type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `styleSheet` satisfies the SafeStyleSheet type
|
||||
* contract in all possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} styleSheet The string to wrap as a SafeStyleSheet.
|
||||
* @return {!goog.html.SafeStyleSheet} The value of `styleSheet`, wrapped
|
||||
* in a SafeStyleSheet object.
|
||||
*/
|
||||
goog.html.uncheckedconversions
|
||||
.safeStyleSheetFromStringKnownToSatisfyTypeContract = function(
|
||||
justification, styleSheet) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.SafeStyleSheet
|
||||
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to SafeUrl from a plain string that is
|
||||
* known to satisfy the SafeUrl type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `url` satisfies the SafeUrl type contract in all
|
||||
* possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} url The string to wrap as a SafeUrl.
|
||||
* @return {!goog.html.SafeUrl} The value of `url`, wrapped in a SafeUrl
|
||||
* object.
|
||||
*/
|
||||
goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
|
||||
function(justification, url) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
|
||||
* that is known to satisfy the TrustedResourceUrl type contract.
|
||||
*
|
||||
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
|
||||
* that the value of `url` satisfies the TrustedResourceUrl type contract
|
||||
* in all possible program states.
|
||||
*
|
||||
*
|
||||
* @param {!goog.string.Const} justification A constant string explaining why
|
||||
* this use of this method is safe. May include a security review ticket
|
||||
* number.
|
||||
* @param {string} url The string to wrap as a TrustedResourceUrl.
|
||||
* @return {!goog.html.TrustedResourceUrl} The value of `url`, wrapped in
|
||||
* a TrustedResourceUrl object.
|
||||
*/
|
||||
goog.html.uncheckedconversions
|
||||
.trustedResourceUrlFromStringKnownToSatisfyTypeContract = function(
|
||||
justification, url) {
|
||||
'use strict';
|
||||
// unwrap() called inside an assert so that justification can be optimized
|
||||
// away in production code.
|
||||
goog.asserts.assertString(
|
||||
goog.string.Const.unwrap(justification), 'must provide justification');
|
||||
goog.asserts.assert(
|
||||
!goog.string.internal.isEmptyOrWhitespace(
|
||||
goog.string.Const.unwrap(justification)),
|
||||
'must provide non-empty justification');
|
||||
return goog.html.TrustedResourceUrl
|
||||
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
|
||||
};
|
||||
Executable
+943
@@ -0,0 +1,943 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utility functions for supporting Bidi issues.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for bidi supporting functions.
|
||||
*/
|
||||
goog.provide('goog.i18n.bidi');
|
||||
goog.provide('goog.i18n.bidi.Dir');
|
||||
goog.provide('goog.i18n.bidi.DirectionalString');
|
||||
goog.provide('goog.i18n.bidi.Format');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
|
||||
* to say that the current locale is a RTL locale. This should only be used
|
||||
* if you want to override the default behavior for deciding whether the
|
||||
* current locale is RTL or not.
|
||||
*
|
||||
* {@see goog.i18n.bidi.IS_RTL}
|
||||
*/
|
||||
goog.i18n.bidi.FORCE_RTL = goog.define('goog.i18n.bidi.FORCE_RTL', false);
|
||||
|
||||
|
||||
/**
|
||||
* Constant that defines whether or not the current locale is a RTL locale.
|
||||
* If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
|
||||
* to check that {@link goog.LOCALE} is one of a few major RTL locales.
|
||||
*
|
||||
* <p>This is designed to be a maximally efficient compile-time constant. For
|
||||
* example, for the default goog.LOCALE, compiling
|
||||
* "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
|
||||
* is this design consideration that limits the implementation to only
|
||||
* supporting a few major RTL locales, as opposed to the broader repertoire of
|
||||
* something like goog.i18n.bidi.isRtlLanguage.
|
||||
*
|
||||
* <p>Since this constant refers to the directionality of the locale, it is up
|
||||
* to the caller to determine if this constant should also be used for the
|
||||
* direction of the UI.
|
||||
*
|
||||
* {@see goog.LOCALE}
|
||||
*
|
||||
* @type {boolean}
|
||||
*
|
||||
* TODO(user): write a test that checks that this is a compile-time constant.
|
||||
*/
|
||||
// LINT.IfChange
|
||||
goog.i18n.bidi.IS_RTL =
|
||||
goog.i18n.bidi.FORCE_RTL ||
|
||||
((goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
|
||||
goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
|
||||
(goog.LOCALE.length == 2 || goog.LOCALE.substring(2, 3) == '-' ||
|
||||
goog.LOCALE.substring(2, 3) == '_')) ||
|
||||
( // Specific to CKB (Central Kurdish)
|
||||
goog.LOCALE.length >= 3 &&
|
||||
goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
|
||||
(goog.LOCALE.length == 3 || goog.LOCALE.substring(3, 4) == '-' ||
|
||||
goog.LOCALE.substring(3, 4) == '_')) ||
|
||||
( // 2 letter language codes with RTL scripts
|
||||
goog.LOCALE.length >= 7 &&
|
||||
((goog.LOCALE.substring(2, 3) == '-' ||
|
||||
goog.LOCALE.substring(2, 3) == '_') &&
|
||||
(goog.LOCALE.substring(3, 7).toLowerCase() == 'adlm' ||
|
||||
goog.LOCALE.substring(3, 7).toLowerCase() == 'arab' ||
|
||||
goog.LOCALE.substring(3, 7).toLowerCase() == 'hebr' ||
|
||||
goog.LOCALE.substring(3, 7).toLowerCase() == 'nkoo' ||
|
||||
goog.LOCALE.substring(3, 7).toLowerCase() == 'rohg' ||
|
||||
goog.LOCALE.substring(3, 7).toLowerCase() == 'thaa'))) ||
|
||||
( // 3 letter languages codes with RTL scripts
|
||||
goog.LOCALE.length >= 8 &&
|
||||
((goog.LOCALE.substring(3, 4) == '-' ||
|
||||
goog.LOCALE.substring(3, 4) == '_') &&
|
||||
(goog.LOCALE.substring(4, 8).toLowerCase() == 'adlm' ||
|
||||
goog.LOCALE.substring(4, 8).toLowerCase() == 'arab' ||
|
||||
goog.LOCALE.substring(4, 8).toLowerCase() == 'hebr' ||
|
||||
goog.LOCALE.substring(4, 8).toLowerCase() == 'nkoo' ||
|
||||
goog.LOCALE.substring(4, 8).toLowerCase() == 'rohg' ||
|
||||
goog.LOCALE.substring(4, 8).toLowerCase() == 'thaa')));
|
||||
// closure/RtlLocalesTest.java)
|
||||
|
||||
// TODO(user): Add additional scripts and languages that are RTL,
|
||||
// e.g., mende, samaritan, etc.
|
||||
|
||||
|
||||
/**
|
||||
* Unicode formatting characters and directionality string constants.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.i18n.bidi.Format = {
|
||||
/** Unicode "Left-To-Right Embedding" (LRE) character. */
|
||||
LRE: '\u202A',
|
||||
/** Unicode "Right-To-Left Embedding" (RLE) character. */
|
||||
RLE: '\u202B',
|
||||
/** Unicode "Pop Directional Formatting" (PDF) character. */
|
||||
PDF: '\u202C',
|
||||
/** Unicode "Left-To-Right Mark" (LRM) character. */
|
||||
LRM: '\u200E',
|
||||
/** Unicode "Right-To-Left Mark" (RLM) character. */
|
||||
RLM: '\u200F'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Directionality enum.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.i18n.bidi.Dir = {
|
||||
/**
|
||||
* Left-to-right.
|
||||
*/
|
||||
LTR: 1,
|
||||
|
||||
/**
|
||||
* Right-to-left.
|
||||
*/
|
||||
RTL: -1,
|
||||
|
||||
/**
|
||||
* Neither left-to-right nor right-to-left.
|
||||
*/
|
||||
NEUTRAL: 0
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 'right' string constant.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.i18n.bidi.RIGHT = 'right';
|
||||
|
||||
|
||||
/**
|
||||
* 'left' string constant.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.i18n.bidi.LEFT = 'left';
|
||||
|
||||
|
||||
/**
|
||||
* 'left' if locale is RTL, 'right' if not.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.i18n.bidi.I18N_RIGHT =
|
||||
goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : goog.i18n.bidi.RIGHT;
|
||||
|
||||
|
||||
/**
|
||||
* 'right' if locale is RTL, 'left' if not.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.i18n.bidi.I18N_LEFT =
|
||||
goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
|
||||
|
||||
|
||||
/**
|
||||
* Convert a directionality given in various formats to a goog.i18n.bidi.Dir
|
||||
* constant. Useful for interaction with different standards of directionality
|
||||
* representation.
|
||||
*
|
||||
* @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
|
||||
* in one of the following formats:
|
||||
* 1. A goog.i18n.bidi.Dir constant.
|
||||
* 2. A number (positive = LTR, negative = RTL, 0 = neutral).
|
||||
* 3. A boolean (true = RTL, false = LTR).
|
||||
* 4. A null for unknown directionality.
|
||||
* @param {boolean=} opt_noNeutral Whether a givenDir of zero or
|
||||
* goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
|
||||
* order to preserve legacy behavior.
|
||||
* @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
|
||||
* given directionality. If given null, returns null (i.e. unknown).
|
||||
*/
|
||||
goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
|
||||
'use strict';
|
||||
if (typeof givenDir == 'number') {
|
||||
// This includes the non-null goog.i18n.bidi.Dir case.
|
||||
return givenDir > 0 ?
|
||||
goog.i18n.bidi.Dir.LTR :
|
||||
givenDir < 0 ? goog.i18n.bidi.Dir.RTL :
|
||||
opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
|
||||
} else if (givenDir == null) {
|
||||
return null;
|
||||
} else {
|
||||
// Must be typeof givenDir == 'boolean'.
|
||||
return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A practical pattern to identify strong LTR character in the BMP.
|
||||
* This pattern is not theoretically correct according to the Unicode
|
||||
* standard. It is simplified for performance and small code size.
|
||||
* It also partially supports LTR scripts beyond U+FFFF by including
|
||||
* UTF-16 high surrogate values corresponding to mostly L-class code
|
||||
* point ranges.
|
||||
* However, low surrogate values and private-use regions are not included
|
||||
* in this RegEx.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.ltrChars_ =
|
||||
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0900-\u1FFF' +
|
||||
'\u200E\u2C00-\uD801\uD804-\uD839\uD83C-\uDBFF' +
|
||||
'\uF900-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
|
||||
|
||||
/**
|
||||
* A practical pattern to identify strong RTL character. This pattern is not
|
||||
* theoretically correct according to the Unicode standard. It is simplified
|
||||
* for performance and small code size.
|
||||
* It also partially supports RTL scripts beyond U+FFFF by including
|
||||
* UTF-16 high surrogate values corresponding to mostly R- or AL-class
|
||||
* code point ranges.
|
||||
* However, low surrogate values and private-use regions are not included
|
||||
* in this RegEx.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlChars_ =
|
||||
'\u0591-\u06EF\u06FA-\u08FF\u200F\uD802-\uD803\uD83A-\uD83B' +
|
||||
'\uFB1D-\uFDFF\uFE70-\uFEFC';
|
||||
|
||||
/**
|
||||
* Simplified regular expression for an HTML tag (opening or closing) or an HTML
|
||||
* escape. We might want to skip over such expressions when estimating the text
|
||||
* directionality.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the input text with spaces instead of HTML tags or HTML escapes, if
|
||||
* opt_isStripNeeded is true. Else returns the input as is.
|
||||
* Useful for text directionality estimation.
|
||||
* Note: the function should not be used in other contexts; it is not 100%
|
||||
* correct, but rather a good-enough implementation for directionality
|
||||
* estimation purposes.
|
||||
* @param {string} str The given string.
|
||||
* @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
|
||||
* Default: false (to retain consistency with calling functions).
|
||||
* @return {string} The given string cleaned of HTML tags / escapes.
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
|
||||
'use strict';
|
||||
return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : str;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to check for RTL characters, BMP and high surrogate.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to check for LTR characters.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Test whether the given string has any RTL characters in it.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether the string contains RTL characters.
|
||||
*/
|
||||
goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.rtlCharReg_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test whether the given string has any RTL characters in it.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @return {boolean} Whether the string contains RTL characters.
|
||||
* @deprecated Use hasAnyRtl.
|
||||
*/
|
||||
goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
|
||||
|
||||
|
||||
/**
|
||||
* Test whether the given string has any LTR characters in it.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether the string contains LTR characters.
|
||||
*/
|
||||
goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.ltrCharReg_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression pattern to check if the first character in the string
|
||||
* is LTR.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression pattern to check if the first character in the string
|
||||
* is RTL.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Check if the first character in the string is RTL or not.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @return {boolean} Whether the first character in str is an RTL char.
|
||||
*/
|
||||
goog.i18n.bidi.isRtlChar = function(str) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.rtlRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the first character in the string is LTR or not.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @return {boolean} Whether the first character in str is an LTR char.
|
||||
*/
|
||||
goog.i18n.bidi.isLtrChar = function(str) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.ltrRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the first character in the string is neutral or not.
|
||||
* @param {string} str The given string that need to be tested.
|
||||
* @return {boolean} Whether the first character in str is a neutral char.
|
||||
*/
|
||||
goog.i18n.bidi.isNeutralChar = function(str) {
|
||||
'use strict';
|
||||
return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expressions to check if a piece of text is of LTR directionality
|
||||
* on first character with strong directionality.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
|
||||
'^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Regular expressions to check if a piece of text is of RTL directionality
|
||||
* on first character with strong directionality.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
|
||||
'^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the first strongly directional character (if any) is RTL.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether RTL directionality is detected using the first
|
||||
* strongly-directional character method.
|
||||
*/
|
||||
goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.rtlDirCheckRe_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the first strongly directional character (if any) is RTL.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether RTL directionality is detected using the first
|
||||
* strongly-directional character method.
|
||||
* @deprecated Use startsWithRtl.
|
||||
*/
|
||||
goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the first strongly directional character (if any) is LTR.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether LTR directionality is detected using the first
|
||||
* strongly-directional character method.
|
||||
*/
|
||||
goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.ltrDirCheckRe_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the first strongly directional character (if any) is LTR.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether LTR directionality is detected using the first
|
||||
* strongly-directional character method.
|
||||
* @deprecated Use startsWithLtr.
|
||||
*/
|
||||
goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to check if a string looks like something that must
|
||||
* always be LTR even in RTL text, e.g. a URL. When estimating the
|
||||
* directionality of text containing these, we treat these as weakly LTR,
|
||||
* like numbers.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the input string either contains no strongly directional
|
||||
* characters or looks like a url.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether neutral directionality is detected.
|
||||
*/
|
||||
goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
|
||||
return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
|
||||
!goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expressions to check if the last strongly-directional character in a
|
||||
* piece of text is LTR.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
|
||||
'[' + goog.i18n.bidi.ltrChars_ + ']' +
|
||||
'[^' + goog.i18n.bidi.rtlChars_ + ']*$');
|
||||
|
||||
|
||||
/**
|
||||
* Regular expressions to check if the last strongly-directional character in a
|
||||
* piece of text is RTL.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
|
||||
'[' + goog.i18n.bidi.rtlChars_ + ']' +
|
||||
'[^' + goog.i18n.bidi.ltrChars_ + ']*$');
|
||||
|
||||
|
||||
/**
|
||||
* Check if the exit directionality a piece of text is LTR, i.e. if the last
|
||||
* strongly-directional character in the string is LTR.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether LTR exit directionality was detected.
|
||||
*/
|
||||
goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.ltrExitDirCheckRe_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the exit directionality a piece of text is LTR, i.e. if the last
|
||||
* strongly-directional character in the string is LTR.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether LTR exit directionality was detected.
|
||||
* @deprecated Use endsWithLtr.
|
||||
*/
|
||||
goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
|
||||
|
||||
|
||||
/**
|
||||
* Check if the exit directionality a piece of text is RTL, i.e. if the last
|
||||
* strongly-directional character in the string is RTL.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether RTL exit directionality was detected.
|
||||
*/
|
||||
goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.rtlExitDirCheckRe_.test(
|
||||
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check if the exit directionality a piece of text is RTL, i.e. if the last
|
||||
* strongly-directional character in the string is RTL.
|
||||
* @param {string} str String being checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether RTL exit directionality was detected.
|
||||
* @deprecated Use endsWithRtl.
|
||||
*/
|
||||
goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
|
||||
|
||||
|
||||
/**
|
||||
* A regular expression for matching right-to-left language codes.
|
||||
* See {@link #isRtlLanguage} for the design.
|
||||
* Note that not all RTL scripts are included.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
|
||||
'^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
|
||||
'.*[-_](Adlm|Arab|Hebr|Nkoo|Rohg|Thaa))' +
|
||||
'(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
|
||||
'i');
|
||||
|
||||
|
||||
/**
|
||||
* Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
|
||||
* - a language code explicitly specifying one of the right-to-left scripts,
|
||||
* e.g. "az-Arab", or<p>
|
||||
* - a language code specifying one of the languages normally written in a
|
||||
* right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
|
||||
* Latin or Cyrillic script (which are the usual LTR alternatives).<p>
|
||||
* The list of right-to-left scripts appears in the 100-199 range in
|
||||
* http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
|
||||
* Hebrew are by far the most widely used. We also recognize Thaana, and N'Ko,
|
||||
* which also have significant modern usage. Adlam and Rohingya
|
||||
* scripts are now included since they can be expected to be used in the
|
||||
* future. The rest (Syriac, Samaritan, Mandaic, etc.) seem to have extremely
|
||||
* limited or no modern usage and are not recognized to save on code size. The
|
||||
* languages usually written in a right-to-left script are taken as those with
|
||||
* Suppress-Script: Hebr|Arab|Thaa|Nkoo|Adlm|Rohg in
|
||||
* http://www.iana.org/assignments/language-subtag-registry,
|
||||
* as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
|
||||
* Other subtags of the language code, e.g. regions like EG (Egypt), are
|
||||
* ignored.
|
||||
* @param {string} lang BCP 47 (a.k.a III) language code.
|
||||
* @return {boolean} Whether the language code is an RTL language.
|
||||
*/
|
||||
goog.i18n.bidi.isRtlLanguage = function(lang) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.rtlLocalesRe_.test(lang);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for bracket guard replacement in text.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.bracketGuardTextRe_ =
|
||||
/(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
|
||||
|
||||
|
||||
/**
|
||||
* Apply bracket guard using LRM and RLM. This is to address the problem of
|
||||
* messy bracket display frequently happens in RTL layout.
|
||||
* This function works for plain text, not for HTML. In HTML, the opening
|
||||
* bracket might be in a different context than the closing bracket (such as
|
||||
* an attribute value).
|
||||
* @param {string} s The string that need to be processed.
|
||||
* @param {boolean=} opt_isRtlContext specifies default direction (usually
|
||||
* direction of the UI).
|
||||
* @return {string} The processed string, with all bracket guarded.
|
||||
*/
|
||||
goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
|
||||
'use strict';
|
||||
const useRtl = opt_isRtlContext === undefined ? goog.i18n.bidi.hasAnyRtl(s) :
|
||||
opt_isRtlContext;
|
||||
const mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
|
||||
return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enforce the html snippet in RTL directionality regardless of overall context.
|
||||
* If the html piece was enclosed by tag, dir will be applied to existing
|
||||
* tag, otherwise a span tag will be added as wrapper. For this reason, if
|
||||
* html snippet starts with a tag, this tag must enclose the whole piece. If
|
||||
* the tag already has a dir specified, this new one will override existing
|
||||
* one in behavior (tested on FF and IE).
|
||||
* @param {string} html The string that need to be processed.
|
||||
* @return {string} The processed string, with directionality enforced to RTL.
|
||||
*/
|
||||
goog.i18n.bidi.enforceRtlInHtml = function(html) {
|
||||
'use strict';
|
||||
if (html.charAt(0) == '<') {
|
||||
return html.replace(/<\w+/, '$& dir=rtl');
|
||||
}
|
||||
// '\n' is important for FF so that it won't incorrectly merge span groups
|
||||
return '\n<span dir=rtl>' + html + '</span>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enforce RTL on both end of the given text piece using unicode BiDi formatting
|
||||
* characters RLE and PDF.
|
||||
* @param {string} text The piece of text that need to be wrapped.
|
||||
* @return {string} The wrapped string after process.
|
||||
*/
|
||||
goog.i18n.bidi.enforceRtlInText = function(text) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enforce the html snippet in RTL directionality regardless or overall context.
|
||||
* If the html piece was enclosed by tag, dir will be applied to existing
|
||||
* tag, otherwise a span tag will be added as wrapper. For this reason, if
|
||||
* html snippet starts with a tag, this tag must enclose the whole piece. If
|
||||
* the tag already has a dir specified, this new one will override existing
|
||||
* one in behavior (tested on FF and IE).
|
||||
* @param {string} html The string that need to be processed.
|
||||
* @return {string} The processed string, with directionality enforced to RTL.
|
||||
*/
|
||||
goog.i18n.bidi.enforceLtrInHtml = function(html) {
|
||||
'use strict';
|
||||
if (html.charAt(0) == '<') {
|
||||
return html.replace(/<\w+/, '$& dir=ltr');
|
||||
}
|
||||
// '\n' is important for FF so that it won't incorrectly merge span groups
|
||||
return '\n<span dir=ltr>' + html + '</span>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enforce LTR on both end of the given text piece using unicode BiDi formatting
|
||||
* characters LRE and PDF.
|
||||
* @param {string} text The piece of text that need to be wrapped.
|
||||
* @return {string} The wrapped string after process.
|
||||
*/
|
||||
goog.i18n.bidi.enforceLtrInText = function(text) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.dimensionsRe_ =
|
||||
/:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for left.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.leftRe_ = /left/gi;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for right.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rightRe_ = /right/gi;
|
||||
|
||||
|
||||
/**
|
||||
* Placeholder regular expression for swapping.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.tempRe_ = /%%%%/g;
|
||||
|
||||
|
||||
/**
|
||||
* Swap location parameters and 'left'/'right' in CSS specification. The
|
||||
* processed string will be suited for RTL layout. Though this function can
|
||||
* cover most cases, there are always exceptions. It is suggested to put
|
||||
* those exceptions in separate group of CSS string.
|
||||
* @param {string} cssStr CSS spefication string.
|
||||
* @return {string} Processed CSS specification string.
|
||||
*/
|
||||
goog.i18n.bidi.mirrorCSS = function(cssStr) {
|
||||
'use strict';
|
||||
return cssStr
|
||||
.
|
||||
// reverse dimensions
|
||||
replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2')
|
||||
.replace(goog.i18n.bidi.leftRe_, '%%%%')
|
||||
. // swap left and right
|
||||
replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT)
|
||||
.replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for hebrew double quote substitution, finding quote
|
||||
* directly after hebrew characters.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for hebrew single quote substitution, finding quote
|
||||
* directly after hebrew characters.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
|
||||
|
||||
|
||||
/**
|
||||
* Replace the double and single quote directly after a Hebrew character with
|
||||
* GERESH and GERSHAYIM. In such case, most likely that's user intention.
|
||||
* @param {string} str String that need to be processed.
|
||||
* @return {string} Processed string with double/single quote replaced.
|
||||
*/
|
||||
goog.i18n.bidi.normalizeHebrewQuote = function(str) {
|
||||
'use strict';
|
||||
return str.replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4')
|
||||
.replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to split a string into "words" for directionality
|
||||
* estimation based on relative word counts.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression to check if a string contains any numerals. Used to
|
||||
* differentiate between completely neutral strings and those containing
|
||||
* numbers, which are weakly LTR.
|
||||
*
|
||||
* Native Arabic digits (\u0660 - \u0669) are not included because although they
|
||||
* do flow left-to-right inside a number, this is the case even if the overall
|
||||
* directionality is RTL, and a mathematical expression using these digits is
|
||||
* supposed to flow right-to-left overall, including unary plus and minus
|
||||
* appearing to the right of a number, and this does depend on the overall
|
||||
* directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the
|
||||
* other hand, are included, since Farsi math (including unary plus and minus)
|
||||
* does flow left-to-right.
|
||||
* TODO: Consider other systems of digits, e.g., Adlam.
|
||||
*
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/;
|
||||
|
||||
|
||||
/**
|
||||
* This constant controls threshold of RTL directionality.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
|
||||
|
||||
|
||||
/**
|
||||
* Estimates the directionality of a string based on relative word counts.
|
||||
* If the number of RTL words is above a certain percentage of the total number
|
||||
* of strongly directional words, returns RTL.
|
||||
* Otherwise, if any words are strongly or weakly LTR, returns LTR.
|
||||
* Otherwise, returns UNKNOWN, which is used to mean "neutral".
|
||||
* Numbers are counted as weakly LTR.
|
||||
* @param {string} str The string to be checked.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {goog.i18n.bidi.Dir} Estimated overall directionality of `str`.
|
||||
*/
|
||||
goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
let rtlCount = 0;
|
||||
let totalCount = 0;
|
||||
let hasWeaklyLtr = false;
|
||||
const tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)
|
||||
.split(goog.i18n.bidi.wordSeparatorRe_);
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
if (goog.i18n.bidi.startsWithRtl(token)) {
|
||||
rtlCount++;
|
||||
totalCount++;
|
||||
} else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
|
||||
hasWeaklyLtr = true;
|
||||
} else if (goog.i18n.bidi.hasAnyLtr(token)) {
|
||||
totalCount++;
|
||||
} else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
|
||||
hasWeaklyLtr = true;
|
||||
}
|
||||
}
|
||||
|
||||
return totalCount == 0 ?
|
||||
(hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
|
||||
(rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
|
||||
goog.i18n.bidi.Dir.RTL :
|
||||
goog.i18n.bidi.Dir.LTR);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check the directionality of a piece of text, return true if the piece of
|
||||
* text should be laid out in RTL direction.
|
||||
* @param {string} str The piece of text that need to be detected.
|
||||
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
||||
* Default: false.
|
||||
* @return {boolean} Whether this piece of text should be laid out in RTL.
|
||||
*/
|
||||
goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
|
||||
'use strict';
|
||||
return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
|
||||
goog.i18n.bidi.Dir.RTL;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets text input element's directionality and text alignment based on a
|
||||
* given directionality. Does nothing if the given directionality is unknown or
|
||||
* neutral.
|
||||
* @param {Element} element Input field element to set directionality to.
|
||||
* @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
|
||||
* given in one of the following formats:
|
||||
* 1. A goog.i18n.bidi.Dir constant.
|
||||
* 2. A number (positive = LRT, negative = RTL, 0 = neutral).
|
||||
* 3. A boolean (true = RTL, false = LTR).
|
||||
* 4. A null for unknown directionality.
|
||||
* @return {void}
|
||||
*/
|
||||
goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
|
||||
'use strict';
|
||||
if (element) {
|
||||
const htmlElement = /** @type {!HTMLElement} */ (element);
|
||||
dir = goog.i18n.bidi.toDir(dir);
|
||||
if (dir) {
|
||||
htmlElement.style.textAlign = dir == goog.i18n.bidi.Dir.RTL ?
|
||||
goog.i18n.bidi.RIGHT :
|
||||
goog.i18n.bidi.LEFT;
|
||||
htmlElement.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets element dir based on estimated directionality of the given text.
|
||||
* @param {!Element} element
|
||||
* @param {string} text
|
||||
* @return {void}
|
||||
*/
|
||||
goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) {
|
||||
'use strict';
|
||||
const htmlElement = /** @type {!HTMLElement} */ (element);
|
||||
switch (goog.i18n.bidi.estimateDirection(text)) {
|
||||
case (goog.i18n.bidi.Dir.LTR):
|
||||
if (htmlElement.dir !== 'ltr') {
|
||||
htmlElement.dir = 'ltr';
|
||||
}
|
||||
break;
|
||||
case (goog.i18n.bidi.Dir.RTL):
|
||||
if (htmlElement.dir !== 'rtl') {
|
||||
htmlElement.dir = 'rtl';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Default for no direction, inherit from document.
|
||||
htmlElement.removeAttribute('dir');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Strings that have an (optional) known direction.
|
||||
*
|
||||
* Implementations of this interface are string-like objects that carry an
|
||||
* attached direction, if known.
|
||||
* @interface
|
||||
*/
|
||||
goog.i18n.bidi.DirectionalString = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Interface marker of the DirectionalString interface.
|
||||
*
|
||||
* This property can be used to determine at runtime whether or not an object
|
||||
* implements this interface. All implementations of this interface set this
|
||||
* property to `true`.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.i18n.bidi.DirectionalString.prototype
|
||||
.implementsGoogI18nBidiDirectionalString;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves this object's known direction (if any).
|
||||
* @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
|
||||
*/
|
||||
goog.i18n.bidi.DirectionalString.prototype.getDirection;
|
||||
Executable
+201
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Shims between goog.iter.Iterator and ES6 iterator.
|
||||
*/
|
||||
|
||||
goog.module('goog.iter.es6');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const GoogIterable = goog.require('goog.iter.Iterable');
|
||||
const GoogIterator = goog.require('goog.iter.Iterator');
|
||||
const StopIteration = goog.require('goog.iter.StopIteration');
|
||||
|
||||
|
||||
/**
|
||||
* Common interface extending both `goog.iter.Iterable` and ES6 `Iterable`,
|
||||
* and providing `toGoog()` and `toEs6()` methods to get either kind
|
||||
* of iterator. `ShimIterable.of()` is the primary entry point for
|
||||
* this library. If it is given an iterable that is *not* also an
|
||||
* iterator, then it will inherit any reusability from its argument
|
||||
* (i.e. `ShimIterable.of(mySet)` will be reusable, since mySet makes
|
||||
* a fresh Iterator every time, whereas `ShimIterable.of(myIterator)`
|
||||
* will be one-shot).
|
||||
*
|
||||
* `ShimGoogIterator` and `ShimEs6Iterator` extend `ShimIterable` and
|
||||
* also implement one or the other iterator API. Since they extend
|
||||
* `ShimIterable`, it is easy to convert back and forth between the two
|
||||
* APIs. Any such conversion will expose a view to the same underlying
|
||||
* iterator, so elements pulled via one API will not be available from
|
||||
* the other.
|
||||
*
|
||||
* @interface
|
||||
* @extends {Iterable<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
class ShimIterable {
|
||||
/** @return {!GoogIterator<VALUE>} */
|
||||
__iterator__() {}
|
||||
|
||||
/** @return {!ShimGoogIterator<VALUE>} */
|
||||
toGoog() {}
|
||||
|
||||
/** @return {!ShimEs6Iterator<VALUE>} */
|
||||
toEs6() {}
|
||||
|
||||
/**
|
||||
* @param {!Iterable<VALUE>|!Iterator<VALUE>|
|
||||
* !GoogIterator<VALUE>|!GoogIterable} iter
|
||||
* @return {!ShimIterable}
|
||||
* @template VALUE
|
||||
*/
|
||||
static of(iter) {
|
||||
if (iter instanceof ShimIterableImpl || iter instanceof ShimGoogIterator ||
|
||||
iter instanceof ShimEs6Iterator) {
|
||||
return iter;
|
||||
} else if (typeof iter.nextValueOrThrow == 'function') {
|
||||
return new ShimIterableImpl(
|
||||
() => wrapGoog(/** @type {!Iterator|!GoogIterator} */ (iter)));
|
||||
} else if (typeof iter[Symbol.iterator] == 'function') {
|
||||
return new ShimIterableImpl(() => iter[Symbol.iterator]());
|
||||
} else if (typeof iter.__iterator__ == 'function') {
|
||||
return new ShimIterableImpl(
|
||||
() => wrapGoog(
|
||||
/** @type {{__iterator__:function(this:?, boolean=)}} */ (iter)
|
||||
.__iterator__()));
|
||||
}
|
||||
throw new Error('Not an iterator or iterable.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {!GoogIterator<VALUE>|!Iterator<VALUE>} iter
|
||||
* @return {!Iterator<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
const wrapGoog = (iter) => {
|
||||
if (!(iter instanceof GoogIterator)) return iter;
|
||||
let done = false;
|
||||
return /** @type {?} */ ({
|
||||
next() {
|
||||
let value;
|
||||
while (!done) {
|
||||
try {
|
||||
value = iter.nextValueOrThrow();
|
||||
break;
|
||||
} catch (err) {
|
||||
if (err !== StopIteration) throw err;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return {value, done};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Concrete (private) implementation of a non-iterator iterable. This is
|
||||
* separate from the iterator versions since it supports iterables that
|
||||
* are not "one-shot".
|
||||
* @implements {ShimIterable<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
class ShimIterableImpl {
|
||||
/** @param {function(): !Iterator<VALUE>} func */
|
||||
constructor(func) {
|
||||
/** @const @private */
|
||||
this.func_ = func;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
__iterator__() {
|
||||
return new ShimGoogIterator(this.func_());
|
||||
}
|
||||
|
||||
/** @override */
|
||||
toGoog() {
|
||||
return new ShimGoogIterator(this.func_());
|
||||
}
|
||||
|
||||
/** @override */
|
||||
[Symbol.iterator]() {
|
||||
return new ShimEs6Iterator(this.func_());
|
||||
}
|
||||
|
||||
/** @override */
|
||||
toEs6() {
|
||||
return new ShimEs6Iterator(this.func_());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concrete `goog.iter.Iterator` subclass that also implements `ShimIterable`.
|
||||
* @extends {GoogIterator<VALUE>}
|
||||
* @implements {ShimIterable<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
class ShimGoogIterator extends GoogIterator {
|
||||
/** @param {!Iterator<VALUE>} iter */
|
||||
constructor(iter) {
|
||||
super();
|
||||
this.iter_ = iter;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
nextValueOrThrow() {
|
||||
const result = this.iter_.next();
|
||||
if (result.done) throw StopIteration;
|
||||
return result.value;
|
||||
}
|
||||
|
||||
|
||||
/** @override */
|
||||
toGoog() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
[Symbol.iterator]() {
|
||||
return new ShimEs6Iterator(this.iter_);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
toEs6() {
|
||||
return new ShimEs6Iterator(this.iter_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Concrete ES6 `Iterator` that also implements `ShimIterable`.
|
||||
* @implements {IteratorIterable<VALUE>}
|
||||
* @extends {ShimIterableImpl<VALUE>}
|
||||
* @template VALUE
|
||||
*/
|
||||
class ShimEs6Iterator extends ShimIterableImpl {
|
||||
/** @param {!Iterator<VALUE>} iter */
|
||||
constructor(iter) {
|
||||
super(() => iter);
|
||||
/** @const @private */
|
||||
this.iter_ = iter;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
next() {
|
||||
return this.iter_.next();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
exports = {
|
||||
ShimIterable,
|
||||
ShimEs6Iterator,
|
||||
ShimGoogIterator,
|
||||
};
|
||||
Executable
+1463
File diff suppressed because it is too large
Load Diff
Executable
+81
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @fileoverview Utility to attempt native JSON processing, falling back to
|
||||
* goog.json if not available.
|
||||
*
|
||||
* This is intended as a drop-in for current users of goog.json who want
|
||||
* to take advantage of native JSON if present.
|
||||
*/
|
||||
|
||||
goog.provide('goog.json.hybrid');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.json');
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to serialize the JSON string natively, falling back to
|
||||
* `goog.json.serialize` if unsuccessful.
|
||||
* @param {!Object} obj JavaScript object to serialize to JSON.
|
||||
* @return {string} Resulting JSON string.
|
||||
*/
|
||||
goog.json.hybrid.stringify = goog.json.USE_NATIVE_JSON ?
|
||||
goog.global['JSON']['stringify'] :
|
||||
function(obj) {
|
||||
'use strict';
|
||||
if (goog.global.JSON) {
|
||||
try {
|
||||
return goog.global.JSON.stringify(obj);
|
||||
} catch (e) {
|
||||
// Native serialization failed. Fall through to retry with
|
||||
// goog.json.serialize.
|
||||
}
|
||||
}
|
||||
|
||||
return goog.json.serialize(obj);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to parse the JSON string natively, falling back to
|
||||
* the supplied `fallbackParser` if unsuccessful.
|
||||
* @param {string} jsonString JSON string to parse.
|
||||
* @param {function(string):Object} fallbackParser Fallback JSON parser used
|
||||
* if native
|
||||
* @return {?Object} Resulting JSON object.
|
||||
* @private
|
||||
*/
|
||||
goog.json.hybrid.parse_ = function(jsonString, fallbackParser) {
|
||||
'use strict';
|
||||
if (goog.global.JSON) {
|
||||
try {
|
||||
var obj = goog.global.JSON.parse(jsonString);
|
||||
goog.asserts.assert(typeof obj == 'object');
|
||||
return /** @type {?Object} */ (obj);
|
||||
} catch (e) {
|
||||
// Native parse failed. Fall through to retry with goog.json.parse.
|
||||
}
|
||||
}
|
||||
|
||||
return fallbackParser(jsonString);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to parse the JSON string natively, falling back to
|
||||
* `goog.json.parse` if unsuccessful.
|
||||
* @param {string} jsonString JSON string to parse.
|
||||
* @return {?Object} Resulting JSON object.
|
||||
*/
|
||||
goog.json.hybrid.parse = goog.json.USE_NATIVE_JSON ?
|
||||
goog.global['JSON']['parse'] :
|
||||
function(jsonString) {
|
||||
'use strict';
|
||||
return goog.json.hybrid.parse_(jsonString, goog.json.parse);
|
||||
};
|
||||
Executable
+395
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview JSON utility functions.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.json');
|
||||
goog.provide('goog.json.Replacer');
|
||||
goog.provide('goog.json.Reviver');
|
||||
goog.provide('goog.json.Serializer');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, use the native JSON parsing API.
|
||||
* NOTE: The default `goog.json.parse` implementation is able to handle
|
||||
* invalid JSON. JSPB used to produce invalid JSON which is not the case
|
||||
* anymore so this is safe to enable for parsing JSPB. Using native JSON is
|
||||
* faster and safer than the default implementation using `eval`.
|
||||
*/
|
||||
goog.json.USE_NATIVE_JSON = goog.define('goog.json.USE_NATIVE_JSON', false);
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, try the native JSON parsing API first. If it
|
||||
* fails, log an error and use `eval` instead. This is useful when
|
||||
* transitioning to `goog.json.USE_NATIVE_JSON`. The error logger needs to
|
||||
* be set by `goog.json.setErrorLogger`. If it is not set then the error
|
||||
* is ignored.
|
||||
*/
|
||||
goog.json.TRY_NATIVE_JSON = goog.define('goog.json.TRY_NATIVE_JSON', true);
|
||||
|
||||
|
||||
/**
|
||||
* Tests if a string is an invalid JSON string. This only ensures that we are
|
||||
* not using any invalid characters
|
||||
* @param {string} s The string to test.
|
||||
* @return {boolean} True if the input is a valid JSON string.
|
||||
*/
|
||||
goog.json.isValid = function(s) {
|
||||
'use strict';
|
||||
// All empty whitespace is not valid.
|
||||
if (/^\s*$/.test(s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is taken from http://www.json.org/json2.js which is released to the
|
||||
// public domain.
|
||||
// Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
|
||||
// inside strings. We also treat \u2028 and \u2029 as whitespace which they
|
||||
// are in the RFC but IE and Safari does not match \s to these so we need to
|
||||
// include them in the reg exps in all places where whitespace is allowed.
|
||||
// We allowed \x7f inside strings because some tools don't escape it,
|
||||
// e.g. http://www.json.org/java/org/json/JSONObject.java
|
||||
|
||||
// Parsing happens in three stages. In the first stage, we run the text
|
||||
// against regular expressions that look for non-JSON patterns. We are
|
||||
// especially concerned with '()' and 'new' because they can cause invocation,
|
||||
// and '=' because it can cause mutation. But just to be safe, we want to
|
||||
// reject all unexpected forms.
|
||||
|
||||
// We split the first stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace all backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters, but only when followed
|
||||
// by a colon, comma, closing bracket or end of string. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
// Don't make these static since they have the global flag.
|
||||
const backslashesRe = /\\["\\\/bfnrtu]/g;
|
||||
const simpleValuesRe =
|
||||
/(?:"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)[\s\u2028\u2029]*(?=:|,|]|}|$)/g;
|
||||
const openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
|
||||
const remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
|
||||
|
||||
return remainderRe.test(
|
||||
s.replace(backslashesRe, '@')
|
||||
.replace(simpleValuesRe, ']')
|
||||
.replace(openBracketsRe, ''));
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a parsing error in `JSON.parse` solvable by using `eval`
|
||||
* if `goog.json.TRY_NATIVE_JSON` is enabled.
|
||||
* @private {function(string, !Error)} The first parameter is the error message,
|
||||
* the second is the exception thrown by `JSON.parse`.
|
||||
*/
|
||||
goog.json.errorLogger_ = goog.nullFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Sets an error logger to use if there's a recoverable parsing error and
|
||||
* `goog.json.TRY_NATIVE_JSON` is enabled.
|
||||
* @param {function(string, !Error)} errorLogger The first parameter is the
|
||||
* error message, the second is the exception thrown by `JSON.parse`.
|
||||
*/
|
||||
goog.json.setErrorLogger = function(errorLogger) {
|
||||
'use strict';
|
||||
goog.json.errorLogger_ = errorLogger;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a JSON string and returns the result. This throws an exception if
|
||||
* the string is an invalid JSON string.
|
||||
*
|
||||
* Note that this is very slow on large strings. Use JSON.parse if possible.
|
||||
*
|
||||
* @param {*} s The JSON string to parse.
|
||||
* @throws Error if s is invalid JSON.
|
||||
* @return {Object} The object generated from the JSON string, or null.
|
||||
* @deprecated Use JSON.parse.
|
||||
*/
|
||||
goog.json.parse = goog.json.USE_NATIVE_JSON ?
|
||||
/** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
|
||||
function(s) {
|
||||
'use strict';
|
||||
let error;
|
||||
if (goog.json.TRY_NATIVE_JSON) {
|
||||
try {
|
||||
return goog.global['JSON']['parse'](s);
|
||||
} catch (ex) {
|
||||
error = ex;
|
||||
}
|
||||
}
|
||||
const o = String(s);
|
||||
if (goog.json.isValid(o)) {
|
||||
|
||||
try {
|
||||
const result = /** @type {?Object} */ (eval('(' + o + ')'));
|
||||
if (error) {
|
||||
goog.json.errorLogger_('Invalid JSON: ' + o, error);
|
||||
}
|
||||
return result;
|
||||
} catch (ex) {
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid JSON string: ' + o);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
|
||||
* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
|
||||
*
|
||||
* TODO(nicksantos): Array should also be a valid replacer.
|
||||
*
|
||||
* @typedef {function(this:Object, string, *): *}
|
||||
*/
|
||||
goog.json.Replacer;
|
||||
|
||||
|
||||
/**
|
||||
* JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
|
||||
* @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
|
||||
*
|
||||
* @typedef {function(this:Object, string, *): *}
|
||||
*/
|
||||
goog.json.Reviver;
|
||||
|
||||
|
||||
/**
|
||||
* Serializes an object or a value to a JSON string.
|
||||
*
|
||||
* @param {*} object The object to serialize.
|
||||
* @param {?goog.json.Replacer=} opt_replacer A replacer function
|
||||
* called for each (key, value) pair that determines how the value
|
||||
* should be serialized. By defult, this just returns the value
|
||||
* and allows default serialization to kick in.
|
||||
* @throws Error if there are loops in the object graph.
|
||||
* @return {string} A JSON string representation of the input.
|
||||
*/
|
||||
goog.json.serialize = goog.json.USE_NATIVE_JSON ?
|
||||
/** @type {function(*, ?goog.json.Replacer=):string} */
|
||||
(goog.global['JSON']['stringify']) :
|
||||
function(object, opt_replacer) {
|
||||
'use strict';
|
||||
// NOTE(nicksantos): Currently, we never use JSON.stringify.
|
||||
//
|
||||
// The last time I evaluated this, JSON.stringify had subtle bugs and
|
||||
// behavior differences on all browsers, and the performance win was not
|
||||
// large enough to justify all the issues. This may change in the future
|
||||
// as browser implementations get better.
|
||||
//
|
||||
// assertSerialize in json_test contains if branches for the cases
|
||||
// that fail.
|
||||
return new goog.json.Serializer(opt_replacer).serialize(object);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class that is used to serialize JSON objects to a string.
|
||||
* @param {?goog.json.Replacer=} opt_replacer Replacer.
|
||||
* @constructor
|
||||
*/
|
||||
goog.json.Serializer = function(opt_replacer) {
|
||||
'use strict';
|
||||
/**
|
||||
* @type {goog.json.Replacer|null|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.replacer_ = opt_replacer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes an object or a value to a JSON string.
|
||||
*
|
||||
* @param {*} object The object to serialize.
|
||||
* @throws Error if there are loops in the object graph.
|
||||
* @return {string} A JSON string representation of the input.
|
||||
*/
|
||||
goog.json.Serializer.prototype.serialize = function(object) {
|
||||
'use strict';
|
||||
const sb = [];
|
||||
this.serializeInternal(object, sb);
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a generic value to a JSON string
|
||||
* @protected
|
||||
* @param {*} object The object to serialize.
|
||||
* @param {Array<string>} sb Array used as a string builder.
|
||||
* @throws Error if there are loops in the object graph.
|
||||
*/
|
||||
goog.json.Serializer.prototype.serializeInternal = function(object, sb) {
|
||||
'use strict';
|
||||
if (object == null) {
|
||||
// undefined == null so this branch covers undefined as well as null
|
||||
sb.push('null');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof object == 'object') {
|
||||
if (Array.isArray(object)) {
|
||||
this.serializeArray(object, sb);
|
||||
return;
|
||||
} else if (
|
||||
object instanceof String || object instanceof Number ||
|
||||
object instanceof Boolean) {
|
||||
object = object.valueOf();
|
||||
// Fall through to switch below.
|
||||
} else {
|
||||
this.serializeObject_(/** @type {!Object} */ (object), sb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (typeof object) {
|
||||
case 'string':
|
||||
this.serializeString_(object, sb);
|
||||
break;
|
||||
case 'number':
|
||||
this.serializeNumber_(object, sb);
|
||||
break;
|
||||
case 'boolean':
|
||||
sb.push(String(object));
|
||||
break;
|
||||
case 'function':
|
||||
sb.push('null');
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown type: ' + typeof object);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Character mappings used internally for goog.string.quote
|
||||
* @private
|
||||
* @type {!Object}
|
||||
*/
|
||||
goog.json.Serializer.charToJsonCharCache_ = {
|
||||
'\"': '\\"',
|
||||
'\\': '\\\\',
|
||||
'/': '\\/',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
|
||||
'\x0B': '\\u000b' // '\v' is not supported in JScript
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression used to match characters that need to be replaced.
|
||||
* The S60 browser has a bug where unicode characters are not matched by
|
||||
* regular expressions. The condition below detects such behaviour and
|
||||
* adjusts the regular expression accordingly.
|
||||
* @private
|
||||
* @type {!RegExp}
|
||||
*/
|
||||
goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
|
||||
/[\\\"\x00-\x1f\x7f-\uffff]/g :
|
||||
/[\\\"\x00-\x1f\x7f-\xff]/g;
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a string to a JSON string
|
||||
* @private
|
||||
* @param {string} s The string to serialize.
|
||||
* @param {Array<string>} sb Array used as a string builder.
|
||||
*/
|
||||
goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
|
||||
'use strict';
|
||||
// The official JSON implementation does not work with international
|
||||
// characters.
|
||||
sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
|
||||
'use strict';
|
||||
// caching the result improves performance by a factor 2-3
|
||||
let rv = goog.json.Serializer.charToJsonCharCache_[c];
|
||||
if (!rv) {
|
||||
rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).substr(1);
|
||||
goog.json.Serializer.charToJsonCharCache_[c] = rv;
|
||||
}
|
||||
return rv;
|
||||
}), '"');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a number to a JSON string
|
||||
* @private
|
||||
* @param {number} n The number to serialize.
|
||||
* @param {Array<string>} sb Array used as a string builder.
|
||||
*/
|
||||
goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
|
||||
'use strict';
|
||||
sb.push(isFinite(n) && !isNaN(n) ? String(n) : 'null');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes an array to a JSON string
|
||||
* @param {Array<string>} arr The array to serialize.
|
||||
* @param {Array<string>} sb Array used as a string builder.
|
||||
* @protected
|
||||
*/
|
||||
goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
|
||||
'use strict';
|
||||
const l = arr.length;
|
||||
sb.push('[');
|
||||
let sep = '';
|
||||
for (let i = 0; i < l; i++) {
|
||||
sb.push(sep);
|
||||
|
||||
const value = arr[i];
|
||||
this.serializeInternal(
|
||||
this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
|
||||
sb);
|
||||
|
||||
sep = ',';
|
||||
}
|
||||
sb.push(']');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes an object to a JSON string
|
||||
* @private
|
||||
* @param {!Object} obj The object to serialize.
|
||||
* @param {Array<string>} sb Array used as a string builder.
|
||||
*/
|
||||
goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
|
||||
'use strict';
|
||||
sb.push('{');
|
||||
let sep = '';
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const value = obj[key];
|
||||
// Skip functions.
|
||||
if (typeof value != 'function') {
|
||||
sb.push(sep);
|
||||
this.serializeString_(key, sb);
|
||||
sb.push(':');
|
||||
|
||||
this.serializeInternal(
|
||||
this.replacer_ ? this.replacer_.call(obj, key, value) : value, sb);
|
||||
|
||||
sep = ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.push('}');
|
||||
};
|
||||
Executable
+340
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Closure user agent detection (Browser).
|
||||
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
|
||||
* For more information on rendering engine, platform, or device see the other
|
||||
* sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
|
||||
* goog.labs.userAgent.device respectively.)
|
||||
*/
|
||||
|
||||
goog.module('goog.labs.userAgent.browser');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const googArray = goog.require('goog.array');
|
||||
const googObject = goog.require('goog.object');
|
||||
const util = goog.require('goog.labs.userAgent.util');
|
||||
const {compareVersions} = goog.require('goog.string.internal');
|
||||
|
||||
// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
|
||||
// functions.
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether to use navigator.userAgentData to determine
|
||||
* browser's brand.
|
||||
*/
|
||||
function useUserAgentBrand() {
|
||||
const userAgentData = util.getUserAgentData();
|
||||
return !!userAgentData && userAgentData.brands.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Opera. Note: Chromium based
|
||||
* Opera (Opera 15+) is detected as Chrome to avoid unnecessary special
|
||||
* casing.
|
||||
*/
|
||||
function matchOpera() {
|
||||
if (util.ASSUME_CLIENT_HINTS_SUPPORT || util.getUserAgentData()) {
|
||||
// This will remain false for non Chromium based Opera.
|
||||
return false;
|
||||
}
|
||||
return util.matchUserAgent('Opera');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is IE. */
|
||||
function matchIE() {
|
||||
if (util.ASSUME_CLIENT_HINTS_SUPPORT || util.getUserAgentData()) {
|
||||
// This will remain false for IE.
|
||||
return false;
|
||||
}
|
||||
return util.matchUserAgent('Trident') || util.matchUserAgent('MSIE');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Edge. This refers to
|
||||
* EdgeHTML based Edge.
|
||||
*/
|
||||
function matchEdgeHtml() {
|
||||
if (util.ASSUME_CLIENT_HINTS_SUPPORT || util.getUserAgentData()) {
|
||||
// This will remain false for non chromium based Edge.
|
||||
return false;
|
||||
}
|
||||
return util.matchUserAgent('Edge');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Chromium based Edge. */
|
||||
function matchEdgeChromium() {
|
||||
if (useUserAgentBrand()) {
|
||||
return util.matchUserAgentDataBrand('Edge');
|
||||
}
|
||||
return util.matchUserAgent('Edg/');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Chromium based Opera. */
|
||||
function matchOperaChromium() {
|
||||
if (useUserAgentBrand()) {
|
||||
return util.matchUserAgentDataBrand('Opera');
|
||||
}
|
||||
return util.matchUserAgent('OPR');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Firefox. */
|
||||
function matchFirefox() {
|
||||
if (useUserAgentBrand()) {
|
||||
return util.matchUserAgentDataBrand('Firefox');
|
||||
}
|
||||
return util.matchUserAgent('Firefox') || util.matchUserAgent('FxiOS');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Safari. */
|
||||
function matchSafari() {
|
||||
if (useUserAgentBrand()) {
|
||||
// This will always be false before Safari adopt the Client Hint support.
|
||||
return util.matchUserAgentDataBrand('Safari');
|
||||
}
|
||||
return util.matchUserAgent('Safari') &&
|
||||
!(matchChrome() || matchCoast() || matchOpera() || matchEdgeHtml() ||
|
||||
matchEdgeChromium() || matchOperaChromium() || matchFirefox() ||
|
||||
isSilk() || util.matchUserAgent('Android'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
|
||||
* iOS browser).
|
||||
*/
|
||||
function matchCoast() {
|
||||
if (util.ASSUME_CLIENT_HINTS_SUPPORT || util.getUserAgentData()) {
|
||||
// This will remain false for Coast.
|
||||
return false;
|
||||
}
|
||||
return util.matchUserAgent('Coast');
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is iOS Webview. */
|
||||
function matchIosWebview() {
|
||||
// iOS Webview does not show up as Chrome or Safari. Also check for Opera's
|
||||
// WebKit-based iOS browser, Coast.
|
||||
return (util.matchUserAgent('iPad') || util.matchUserAgent('iPhone')) &&
|
||||
!matchSafari() && !matchChrome() && !matchCoast() && !matchFirefox() &&
|
||||
util.matchUserAgent('AppleWebKit');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is any Chromium browser. This
|
||||
* returns true for Chrome, Opera 15+, and Edge Chromium.
|
||||
*/
|
||||
function matchChrome() {
|
||||
if (useUserAgentBrand()) {
|
||||
return util.matchUserAgentDataBrand('Chromium');
|
||||
}
|
||||
return (util.matchUserAgent('Chrome') || util.matchUserAgent('CriOS')) &&
|
||||
!matchEdgeHtml();
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is the Android browser. */
|
||||
function matchAndroidBrowser() {
|
||||
// Android can appear in the user agent string for Chrome on Android.
|
||||
// This is not the Android standalone browser if it does.
|
||||
return util.matchUserAgent('Android') &&
|
||||
!(isChrome() || isFirefox() || isOpera() || isSilk());
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Opera. */
|
||||
const isOpera = matchOpera;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is IE. */
|
||||
const isIE = matchIE;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is EdgeHTML based Edge. */
|
||||
const isEdge = matchEdgeHtml;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Chromium based Edge. */
|
||||
const isEdgeChromium = matchEdgeChromium;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Chromium based Opera. */
|
||||
const isOperaChromium = matchOperaChromium;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Firefox. */
|
||||
const isFirefox = matchFirefox;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is Safari. */
|
||||
const isSafari = matchSafari;
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
|
||||
* iOS browser).
|
||||
*/
|
||||
const isCoast = matchCoast;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is iOS Webview. */
|
||||
const isIosWebview = matchIosWebview;
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user's browser is any Chromium based browser (
|
||||
* Chrome, Blink-based Opera (15+) and Edge Chromium).
|
||||
*/
|
||||
const isChrome = matchChrome;
|
||||
|
||||
/** @return {boolean} Whether the user's browser is the Android browser. */
|
||||
const isAndroidBrowser = matchAndroidBrowser;
|
||||
|
||||
/**
|
||||
* For more information, see:
|
||||
* http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
|
||||
* @return {boolean} Whether the user's browser is Silk.
|
||||
*/
|
||||
function isSilk() {
|
||||
if (useUserAgentBrand()) {
|
||||
return util.matchUserAgentDataBrand('Silk');
|
||||
}
|
||||
return util.matchUserAgent('Silk');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The browser version or empty string if version cannot be
|
||||
* determined. Note that for Internet Explorer, this returns the version of
|
||||
* the browser, not the version of the rendering engine. (IE 8 in
|
||||
* compatibility mode will return 8.0 rather than 7.0. To determine the
|
||||
* rendering engine version, look at document.documentMode instead. See
|
||||
* http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
|
||||
* details.)
|
||||
*/
|
||||
function getVersion() {
|
||||
const userAgentString = util.getUserAgent();
|
||||
// Special case IE since IE's version is inside the parenthesis and
|
||||
// without the '/'.
|
||||
if (isIE()) {
|
||||
return getIEVersion(userAgentString);
|
||||
}
|
||||
|
||||
const versionTuples = util.extractVersionTuples(userAgentString);
|
||||
|
||||
// Construct a map for easy lookup.
|
||||
const versionMap = {};
|
||||
versionTuples.forEach((tuple) => {
|
||||
// Note that the tuple is of length three, but we only care about the
|
||||
// first two.
|
||||
const key = tuple[0];
|
||||
const value = tuple[1];
|
||||
versionMap[key] = value;
|
||||
});
|
||||
|
||||
const versionMapHasKey = goog.partial(googObject.containsKey, versionMap);
|
||||
|
||||
// Gives the value with the first key it finds, otherwise empty string.
|
||||
function lookUpValueWithKeys(keys) {
|
||||
const key = googArray.find(keys, versionMapHasKey);
|
||||
return versionMap[key] || '';
|
||||
}
|
||||
|
||||
// Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
|
||||
// See
|
||||
// http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
|
||||
if (isOpera()) {
|
||||
// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
|
||||
// Opera uses 'OPR' for more recent UAs.
|
||||
return lookUpValueWithKeys(['Version', 'Opera']);
|
||||
}
|
||||
|
||||
// Check Edge before Chrome since it has Chrome in the string.
|
||||
if (isEdge()) {
|
||||
return lookUpValueWithKeys(['Edge']);
|
||||
}
|
||||
|
||||
// Check Chromium Edge before Chrome since it has Chrome in the string.
|
||||
if (isEdgeChromium()) {
|
||||
return lookUpValueWithKeys(['Edg']);
|
||||
}
|
||||
|
||||
if (isChrome()) {
|
||||
return lookUpValueWithKeys(['Chrome', 'CriOS', 'HeadlessChrome']);
|
||||
}
|
||||
|
||||
// Usually products browser versions are in the third tuple after "Mozilla"
|
||||
// and the engine.
|
||||
const tuple = versionTuples[2];
|
||||
return tuple && tuple[1] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the browser version is higher or the same as the
|
||||
* given version.
|
||||
*/
|
||||
function isVersionOrHigher(version) {
|
||||
return compareVersions(getVersion(), version) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines IE version. More information:
|
||||
* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
|
||||
* http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
|
||||
* http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
|
||||
* http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
|
||||
* @param {string} userAgent the User-Agent.
|
||||
* @return {string}
|
||||
*/
|
||||
function getIEVersion(userAgent) {
|
||||
// IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
|
||||
// bug. Example UA:
|
||||
// Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
|
||||
// like Gecko.
|
||||
// See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
|
||||
const rv = /rv: *([\d\.]*)/.exec(userAgent);
|
||||
if (rv && rv[1]) {
|
||||
return rv[1];
|
||||
}
|
||||
|
||||
let version = '';
|
||||
const msie = /MSIE +([\d\.]+)/.exec(userAgent);
|
||||
if (msie && msie[1]) {
|
||||
// IE in compatibility mode usually identifies itself as MSIE 7.0; in this
|
||||
// case, use the Trident version to determine the version of IE. For more
|
||||
// details, see the links above.
|
||||
const tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
|
||||
if (msie[1] == '7.0') {
|
||||
if (tridentVersion && tridentVersion[1]) {
|
||||
switch (tridentVersion[1]) {
|
||||
case '4.0':
|
||||
version = '8.0';
|
||||
break;
|
||||
case '5.0':
|
||||
version = '9.0';
|
||||
break;
|
||||
case '6.0':
|
||||
version = '10.0';
|
||||
break;
|
||||
case '7.0':
|
||||
version = '11.0';
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
version = '7.0';
|
||||
}
|
||||
} else {
|
||||
version = msie[1];
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
exports = {
|
||||
getVersion,
|
||||
isAndroidBrowser,
|
||||
isChrome,
|
||||
isCoast,
|
||||
isEdge,
|
||||
isEdgeChromium,
|
||||
isFirefox,
|
||||
isIE,
|
||||
isIosWebview,
|
||||
isOpera,
|
||||
isOperaChromium,
|
||||
isSafari,
|
||||
isSilk,
|
||||
isVersionOrHigher,
|
||||
};
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Closure user device detection (based on user agent).
|
||||
* @see http://en.wikipedia.org/wiki/User_agent
|
||||
* For more information on browser brand, platform, or engine see the other
|
||||
* sub-namespaces in goog.labs.userAgent (browser, platform, and engine).
|
||||
*/
|
||||
|
||||
goog.provide('goog.labs.userAgent.device');
|
||||
|
||||
goog.require('goog.labs.userAgent.util');
|
||||
|
||||
/**
|
||||
* Currently we detect the iPhone, iPod and Android mobiles (devices that have
|
||||
* both Android and Mobile in the user agent string).
|
||||
*
|
||||
* @return {boolean} Whether the user is using a mobile device.
|
||||
*/
|
||||
goog.labs.userAgent.device.isMobile = function() {
|
||||
'use strict';
|
||||
if (goog.labs.userAgent.util.ASSUME_CLIENT_HINTS_SUPPORT ||
|
||||
goog.labs.userAgent.util.getUserAgentData()) {
|
||||
return goog.labs.userAgent.util.getUserAgentData().mobile;
|
||||
}
|
||||
return !goog.labs.userAgent.device.isTablet() &&
|
||||
(goog.labs.userAgent.util.matchUserAgent('iPod') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('iPhone') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Android') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('IEMobile'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Currently we detect Kindle Fire, iPad, and Android tablets (devices that have
|
||||
* Android but not Mobile in the user agent string).
|
||||
*
|
||||
* @return {boolean} Whether the user is using a tablet.
|
||||
*/
|
||||
goog.labs.userAgent.device.isTablet = function() {
|
||||
'use strict';
|
||||
if (goog.labs.userAgent.util.ASSUME_CLIENT_HINTS_SUPPORT ||
|
||||
goog.labs.userAgent.util.getUserAgentData()) {
|
||||
return !goog.labs.userAgent.util.getUserAgentData().mobile &&
|
||||
(goog.labs.userAgent.util.matchUserAgent('iPad') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Android') ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Silk'));
|
||||
}
|
||||
return goog.labs.userAgent.util.matchUserAgent('iPad') ||
|
||||
(goog.labs.userAgent.util.matchUserAgent('Android') &&
|
||||
!goog.labs.userAgent.util.matchUserAgent('Mobile')) ||
|
||||
goog.labs.userAgent.util.matchUserAgent('Silk');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the user is using a desktop computer (which we
|
||||
* assume to be the case if they are not using either a mobile or tablet
|
||||
* device).
|
||||
*/
|
||||
goog.labs.userAgent.device.isDesktop = function() {
|
||||
'use strict';
|
||||
return !goog.labs.userAgent.device.isMobile() &&
|
||||
!goog.labs.userAgent.device.isTablet();
|
||||
};
|
||||
Executable
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Closure user agent detection.
|
||||
* @see http://en.wikipedia.org/wiki/User_agent
|
||||
* For more information on browser brand, platform, or device see the other
|
||||
* sub-namespaces in goog.labs.userAgent (browser, platform, and device).
|
||||
*/
|
||||
|
||||
goog.module('goog.labs.userAgent.engine');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const googArray = goog.require('goog.array');
|
||||
const googString = goog.require('goog.string.internal');
|
||||
const util = goog.require('goog.labs.userAgent.util');
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Presto.
|
||||
*/
|
||||
function isPresto() {
|
||||
return util.matchUserAgent('Presto');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Trident.
|
||||
*/
|
||||
function isTrident() {
|
||||
// IE only started including the Trident token in IE8.
|
||||
return util.matchUserAgent('Trident') || util.matchUserAgent('MSIE');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is EdgeHTML.
|
||||
*/
|
||||
function isEdge() {
|
||||
return util.matchUserAgent('Edge');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is WebKit. This will return
|
||||
* true for Chrome, Blink-based Opera (15+), Edge Chromium and Safari.
|
||||
*/
|
||||
function isWebKit() {
|
||||
return util.matchUserAgentIgnoreCase('WebKit') && !isEdge();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the rendering engine is Gecko.
|
||||
*/
|
||||
function isGecko() {
|
||||
return util.matchUserAgent('Gecko') && !isWebKit() && !isTrident() &&
|
||||
!isEdge();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} The rendering engine's version or empty string if version
|
||||
* can't be determined.
|
||||
*/
|
||||
function getVersion() {
|
||||
const userAgentString = util.getUserAgent();
|
||||
if (userAgentString) {
|
||||
const tuples = util.extractVersionTuples(userAgentString);
|
||||
|
||||
const engineTuple = getEngineTuple(tuples);
|
||||
if (engineTuple) {
|
||||
// In Gecko, the version string is either in the browser info or the
|
||||
// Firefox version. See Gecko user agent string reference:
|
||||
// http://goo.gl/mULqa
|
||||
if (engineTuple[0] == 'Gecko') {
|
||||
return getVersionForKey(tuples, 'Firefox');
|
||||
}
|
||||
|
||||
return engineTuple[1];
|
||||
}
|
||||
|
||||
// MSIE has only one version identifier, and the Trident version is
|
||||
// specified in the parenthetical. IE Edge is covered in the engine tuple
|
||||
// detection.
|
||||
const browserTuple = tuples[0];
|
||||
let info;
|
||||
if (browserTuple && (info = browserTuple[2])) {
|
||||
const match = /Trident\/([^\s;]+)/.exec(info);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Array<string>>} tuples Extracted version tuples.
|
||||
* @return {!Array<string>|undefined} The engine tuple or undefined if not
|
||||
* found.
|
||||
*/
|
||||
function getEngineTuple(tuples) {
|
||||
if (!isEdge()) {
|
||||
return tuples[1];
|
||||
}
|
||||
for (let i = 0; i < tuples.length; i++) {
|
||||
const tuple = tuples[i];
|
||||
if (tuple[0] == 'Edge') {
|
||||
return tuple;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the rendering engine version is higher or the same
|
||||
* as the given version.
|
||||
*/
|
||||
function isVersionOrHigher(version) {
|
||||
return googString.compareVersions(getVersion(), version) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Array<string>>} tuples Version tuples.
|
||||
* @param {string} key The key to look for.
|
||||
* @return {string} The version string of the given key, if present.
|
||||
* Otherwise, the empty string.
|
||||
*/
|
||||
function getVersionForKey(tuples, key) {
|
||||
// TODO(nnaze): Move to util if useful elsewhere.
|
||||
|
||||
const pair = googArray.find(tuples, function(pair) {
|
||||
return key == pair[0];
|
||||
});
|
||||
|
||||
return pair && pair[1] || '';
|
||||
}
|
||||
|
||||
exports = {
|
||||
getVersion,
|
||||
isEdge,
|
||||
isGecko,
|
||||
isPresto,
|
||||
isTrident,
|
||||
isVersionOrHigher,
|
||||
isWebKit,
|
||||
};
|
||||
Executable
+181
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Closure user agent platform detection.
|
||||
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
|
||||
* For more information on browser brand, rendering engine, or device see the
|
||||
* other sub-namespaces in goog.labs.userAgent (browser, engine, and device
|
||||
* respectively).
|
||||
*/
|
||||
|
||||
goog.module('goog.labs.userAgent.platform');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const googString = goog.require('goog.string.internal');
|
||||
const util = goog.require('goog.labs.userAgent.util');
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Android.
|
||||
*/
|
||||
function isAndroid() {
|
||||
return util.matchUserAgent('Android');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iPod.
|
||||
*/
|
||||
function isIpod() {
|
||||
return util.matchUserAgent('iPod');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is iPhone.
|
||||
*/
|
||||
function isIphone() {
|
||||
return util.matchUserAgent('iPhone') && !util.matchUserAgent('iPod') &&
|
||||
!util.matchUserAgent('iPad');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the platform is iPad.
|
||||
* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in
|
||||
* this scenario the platform will not be recognized as iPad. If you must have
|
||||
* iPad-specific behavior, use
|
||||
* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.
|
||||
* @return {boolean} Whether the platform is iPad.
|
||||
*/
|
||||
function isIpad() {
|
||||
return util.matchUserAgent('iPad');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the platform is iOS.
|
||||
* Note that iPadOS 13+ spoofs macOS Safari by default in its user agent, and in
|
||||
* this scenario the platform will not be recognized as iOS. If you must have
|
||||
* iPad-specific behavior, use
|
||||
* {@link goog.labs.userAgent.extra.isSafariDesktopOnMobile}.
|
||||
* @return {boolean} Whether the platform is iOS.
|
||||
*/
|
||||
function isIos() {
|
||||
return isIphone() || isIpad() || isIpod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Mac.
|
||||
*/
|
||||
function isMacintosh() {
|
||||
return util.matchUserAgent('Macintosh');
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: ChromeOS is not considered to be Linux as it does not report itself
|
||||
* as Linux in the user agent string.
|
||||
* @return {boolean} Whether the platform is Linux.
|
||||
*/
|
||||
function isLinux() {
|
||||
return util.matchUserAgent('Linux');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Windows.
|
||||
*/
|
||||
function isWindows() {
|
||||
return util.matchUserAgent('Windows');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is ChromeOS.
|
||||
*/
|
||||
function isChromeOS() {
|
||||
return util.matchUserAgent('CrOS');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is Chromecast.
|
||||
*/
|
||||
function isChromecast() {
|
||||
return util.matchUserAgent('CrKey');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the platform is KaiOS.
|
||||
*/
|
||||
function isKaiOS() {
|
||||
return util.matchUserAgentIgnoreCase('KaiOS');
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of the platform. We only determine the version for Windows,
|
||||
* Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
|
||||
* look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
|
||||
* version 0.0.
|
||||
*
|
||||
* @return {string} The platform version or empty string if version cannot be
|
||||
* determined.
|
||||
*/
|
||||
function getVersion() {
|
||||
const userAgentString = util.getUserAgent();
|
||||
let version = '', re;
|
||||
if (isWindows()) {
|
||||
re = /Windows (?:NT|Phone) ([0-9.]+)/;
|
||||
const match = re.exec(userAgentString);
|
||||
if (match) {
|
||||
version = match[1];
|
||||
} else {
|
||||
version = '0.0';
|
||||
}
|
||||
} else if (isIos()) {
|
||||
re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
|
||||
const match = re.exec(userAgentString);
|
||||
// Report the version as x.y.z and not x_y_z
|
||||
version = match && match[1].replace(/_/g, '.');
|
||||
} else if (isMacintosh()) {
|
||||
re = /Mac OS X ([0-9_.]+)/;
|
||||
const match = re.exec(userAgentString);
|
||||
// Note: some old versions of Camino do not report an OSX version.
|
||||
// Default to 10.
|
||||
version = match ? match[1].replace(/_/g, '.') : '10';
|
||||
} else if (isKaiOS()) {
|
||||
re = /(?:KaiOS)\/(\S+)/i;
|
||||
const match = re.exec(userAgentString);
|
||||
version = match && match[1];
|
||||
} else if (isAndroid()) {
|
||||
re = /Android\s+([^\);]+)(\)|;)/;
|
||||
const match = re.exec(userAgentString);
|
||||
version = match && match[1];
|
||||
} else if (isChromeOS()) {
|
||||
re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
|
||||
const match = re.exec(userAgentString);
|
||||
version = match && match[1];
|
||||
}
|
||||
return version || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|number} version The version to check.
|
||||
* @return {boolean} Whether the browser version is higher or the same as the
|
||||
* given version.
|
||||
*/
|
||||
function isVersionOrHigher(version) {
|
||||
return googString.compareVersions(getVersion(), version) >= 0;
|
||||
}
|
||||
|
||||
exports = {
|
||||
getVersion,
|
||||
isAndroid,
|
||||
isChromeOS,
|
||||
isChromecast,
|
||||
isIos,
|
||||
isIpad,
|
||||
isIphone,
|
||||
isIpod,
|
||||
isKaiOS,
|
||||
isLinux,
|
||||
isMacintosh,
|
||||
isVersionOrHigher,
|
||||
isWindows,
|
||||
};
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines for goog.labs.userAgent.
|
||||
*/
|
||||
|
||||
goog.module('goog.labs.userAgent');
|
||||
|
||||
/**
|
||||
* @define {string} Optional runtime override for the USE_CLIENT_HINTS flag.
|
||||
* If this is set (for example, to 'foo.bar') then any value of USE_CLIENT_HINTS
|
||||
* will be overridden by `globalThis.foo.bar` if it is non-null.
|
||||
* This flag will be removed in December 2021.
|
||||
*/
|
||||
const USE_CLIENT_HINTS_OVERRIDE =
|
||||
goog.define('goog.labs.userAgent.USE_CLIENT_HINTS_OVERRIDE', '');
|
||||
|
||||
/**
|
||||
* @define {boolean} If true, use navigator.userAgentData
|
||||
* TODO(user) Flip flag in 2021/12.
|
||||
*/
|
||||
const USE_CLIENT_HINTS =
|
||||
goog.define('goog.labs.userAgent.USE_CLIENT_HINTS', false);
|
||||
|
||||
// TODO(user): Replace the IIFE with a simple null-coalescing operator.
|
||||
// NOTE: This can't be done with a helper function, or else we risk an inlining
|
||||
// back-off causing a huge code size regression if a non-inlined helper function
|
||||
// prevents the optimizer from detecting the (possibly large) dead code paths.
|
||||
/** @const {boolean} */
|
||||
exports.USE_CLIENT_HINTS = (() => {
|
||||
const override = USE_CLIENT_HINTS_OVERRIDE ?
|
||||
goog.getObjectByName(USE_CLIENT_HINTS_OVERRIDE) :
|
||||
null;
|
||||
return override != null ? override : USE_CLIENT_HINTS;
|
||||
})();
|
||||
Executable
+211
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities used by goog.labs.userAgent tools. These functions
|
||||
* should not be used outside of goog.labs.userAgent.*.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.module('goog.labs.userAgent.util');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const {USE_CLIENT_HINTS} = goog.require('goog.labs.userAgent');
|
||||
const {caseInsensitiveContains, contains} = goog.require('goog.string.internal');
|
||||
|
||||
/**
|
||||
* @const {boolean} If true, use navigator.userAgentData without check.
|
||||
* TODO(user) FEATURESET_YEAR >= 2022 if it supports mobile and all the
|
||||
* brands we need.
|
||||
*/
|
||||
const ASSUME_CLIENT_HINTS_SUPPORT = false;
|
||||
|
||||
/**
|
||||
* Gets the native userAgent string from navigator if it exists.
|
||||
* If navigator or navigator.userAgent string is missing, returns an empty
|
||||
* string.
|
||||
* @return {string}
|
||||
*/
|
||||
function getNativeUserAgentString() {
|
||||
const navigator = getNavigator();
|
||||
if (navigator) {
|
||||
const userAgent = navigator.userAgent;
|
||||
if (userAgent) {
|
||||
return userAgent;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the native userAgentData object from navigator if it exists.
|
||||
* If navigator.userAgentData object is missing or USE_CLIENT_HINTS is set to
|
||||
* false, returns null.
|
||||
* @return {?NavigatorUAData}
|
||||
*/
|
||||
function getNativeUserAgentData() {
|
||||
if (!USE_CLIENT_HINTS) {
|
||||
return null;
|
||||
}
|
||||
const navigator = getNavigator();
|
||||
// TODO(user): Use navigator?.userAgent ?? null once it's supported.
|
||||
if (navigator) {
|
||||
return navigator.userAgentData || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the native navigator.
|
||||
* @return {!Navigator}
|
||||
*/
|
||||
function getNavigator() {
|
||||
return goog.global.navigator;
|
||||
}
|
||||
|
||||
/**
|
||||
* A possible override for applications which wish to not check
|
||||
* navigator.userAgent but use a specified value for detection instead.
|
||||
* @type {string}
|
||||
*/
|
||||
let userAgentInternal = getNativeUserAgentString();
|
||||
|
||||
/**
|
||||
* A possible override for applications which wish to not check
|
||||
* navigator.userAgentData but use a specified value for detection instead.
|
||||
* @type {?NavigatorUAData}
|
||||
*/
|
||||
let userAgentDataInternal = getNativeUserAgentData();
|
||||
|
||||
/**
|
||||
* Override the user agent string with the given value.
|
||||
* This should only be used for testing within the goog.labs.userAgent
|
||||
* namespace.
|
||||
* Pass `null` to use the native browser object instead.
|
||||
* @param {?string=} userAgent The userAgent override.
|
||||
* @return {void}
|
||||
*/
|
||||
function setUserAgent(userAgent = undefined) {
|
||||
userAgentInternal =
|
||||
typeof userAgent === 'string' ? userAgent : getNativeUserAgentString();
|
||||
}
|
||||
|
||||
/** @return {string} The user agent string. */
|
||||
function getUserAgent() {
|
||||
return userAgentInternal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the user agent data object with the given value.
|
||||
* This should only be used for testing within the goog.labs.userAgent
|
||||
* namespace.
|
||||
* Pass `null` to specify the absence of userAgentData. Note that this behavior
|
||||
* is different from setUserAgent.
|
||||
* @param {?NavigatorUAData} userAgentData The userAgentData override.
|
||||
*/
|
||||
function setUserAgentData(userAgentData) {
|
||||
userAgentDataInternal = userAgentData;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user agent data object was overridden using setUserAgentData,
|
||||
* reset it so that it uses the native browser object instead, if it exists.
|
||||
*/
|
||||
function resetUserAgentData() {
|
||||
userAgentDataInternal = getNativeUserAgentData();
|
||||
}
|
||||
|
||||
/** @return {?NavigatorUAData} Navigator.userAgentData if exist */
|
||||
function getUserAgentData() {
|
||||
return userAgentDataInternal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any string in userAgentData.brands matches str.
|
||||
* Returns false if userAgentData is not supported.
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether any brand string from userAgentData contains the
|
||||
* given string.
|
||||
*/
|
||||
function matchUserAgentDataBrand(str) {
|
||||
const data = getUserAgentData();
|
||||
if (!data) return false;
|
||||
return data.brands.some(({brand}) => brand && contains(brand, str));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether the user agent contains the given string.
|
||||
*/
|
||||
function matchUserAgent(str) {
|
||||
const userAgent = getUserAgent();
|
||||
return contains(userAgent, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @return {boolean} Whether the user agent contains the given string, ignoring
|
||||
* case.
|
||||
*/
|
||||
function matchUserAgentIgnoreCase(str) {
|
||||
const userAgent = getUserAgent();
|
||||
return caseInsensitiveContains(userAgent, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the user agent into tuples for each section.
|
||||
* @param {string} userAgent
|
||||
* @return {!Array<!Array<string>>} Tuples of key, version, and the contents of
|
||||
* the parenthetical.
|
||||
*/
|
||||
function extractVersionTuples(userAgent) {
|
||||
// Matches each section of a user agent string.
|
||||
// Example UA:
|
||||
// Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
|
||||
// AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
|
||||
// This has three version tuples: Mozilla, AppleWebKit, and Mobile.
|
||||
|
||||
const versionRegExp = new RegExp(
|
||||
// Key. Note that a key may have a space.
|
||||
// (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
|
||||
'(\\w[\\w ]+)' +
|
||||
|
||||
'/' + // slash
|
||||
'([^\\s]+)' + // version (i.e. '5.0b')
|
||||
'\\s*' + // whitespace
|
||||
'(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
|
||||
'g');
|
||||
|
||||
const data = [];
|
||||
let match;
|
||||
|
||||
// Iterate and collect the version tuples. Each iteration will be the
|
||||
// next regex match.
|
||||
while (match = versionRegExp.exec(userAgent)) {
|
||||
data.push([
|
||||
match[1], // key
|
||||
match[2], // value
|
||||
// || undefined as this is not undefined in IE7 and IE8
|
||||
match[3] || undefined // info
|
||||
]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports = {
|
||||
ASSUME_CLIENT_HINTS_SUPPORT,
|
||||
extractVersionTuples,
|
||||
getNativeUserAgentString,
|
||||
getUserAgent,
|
||||
getUserAgentData,
|
||||
matchUserAgent,
|
||||
matchUserAgentDataBrand,
|
||||
matchUserAgentIgnoreCase,
|
||||
resetUserAgentData,
|
||||
setUserAgent,
|
||||
setUserAgentData,
|
||||
};
|
||||
Executable
+1084
File diff suppressed because it is too large
Load Diff
Executable
+289
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A utility class for representing two-dimensional positions.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.math.Coordinate');
|
||||
|
||||
goog.require('goog.math');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for representing coordinates and positions.
|
||||
* @param {number=} opt_x Left, defaults to 0.
|
||||
* @param {number=} opt_y Top, defaults to 0.
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
goog.math.Coordinate = function(opt_x, opt_y) {
|
||||
'use strict';
|
||||
/**
|
||||
* X-value
|
||||
* @type {number}
|
||||
*/
|
||||
this.x = (opt_x !== undefined) ? opt_x : 0;
|
||||
|
||||
/**
|
||||
* Y-value
|
||||
* @type {number}
|
||||
*/
|
||||
this.y = (opt_y !== undefined) ? opt_y : 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new copy of the coordinate.
|
||||
* @return {!goog.math.Coordinate} A clone of this coordinate.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.clone = function() {
|
||||
'use strict';
|
||||
return new goog.math.Coordinate(this.x, this.y);
|
||||
};
|
||||
|
||||
|
||||
if (goog.DEBUG) {
|
||||
/**
|
||||
* Returns a nice string representing the coordinate.
|
||||
* @return {string} In the form (50, 73).
|
||||
* @override
|
||||
*/
|
||||
goog.math.Coordinate.prototype.toString = function() {
|
||||
'use strict';
|
||||
return '(' + this.x + ', ' + this.y + ')';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the specified value is equal to this coordinate.
|
||||
* @param {*} other Some other value.
|
||||
* @return {boolean} Whether the specified value is equal to this coordinate.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.equals = function(other) {
|
||||
'use strict';
|
||||
return other instanceof goog.math.Coordinate &&
|
||||
goog.math.Coordinate.equals(this, other);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares coordinates for equality.
|
||||
* @param {goog.math.Coordinate} a A Coordinate.
|
||||
* @param {goog.math.Coordinate} b A Coordinate.
|
||||
* @return {boolean} True iff the coordinates are equal, or if both are null.
|
||||
*/
|
||||
goog.math.Coordinate.equals = function(a, b) {
|
||||
'use strict';
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return a.x == b.x && a.y == b.y;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the distance between two coordinates.
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @param {!goog.math.Coordinate} b A Coordinate.
|
||||
* @return {number} The distance between `a` and `b`.
|
||||
*/
|
||||
goog.math.Coordinate.distance = function(a, b) {
|
||||
'use strict';
|
||||
var dx = a.x - b.x;
|
||||
var dy = a.y - b.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the magnitude of a coordinate.
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @return {number} The distance between the origin and `a`.
|
||||
*/
|
||||
goog.math.Coordinate.magnitude = function(a) {
|
||||
'use strict';
|
||||
return Math.sqrt(a.x * a.x + a.y * a.y);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the angle from the origin to a coordinate.
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @return {number} The angle, in degrees, clockwise from the positive X
|
||||
* axis to `a`.
|
||||
*/
|
||||
goog.math.Coordinate.azimuth = function(a) {
|
||||
'use strict';
|
||||
return goog.math.angle(0, 0, a.x, a.y);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the squared distance between two coordinates. Squared distances can
|
||||
* be used for comparisons when the actual value is not required.
|
||||
*
|
||||
* Performance note: eliminating the square root is an optimization often used
|
||||
* in lower-level languages, but the speed difference is not nearly as
|
||||
* pronounced in JavaScript (only a few percent.)
|
||||
*
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @param {!goog.math.Coordinate} b A Coordinate.
|
||||
* @return {number} The squared distance between `a` and `b`.
|
||||
*/
|
||||
goog.math.Coordinate.squaredDistance = function(a, b) {
|
||||
'use strict';
|
||||
var dx = a.x - b.x;
|
||||
var dy = a.y - b.y;
|
||||
return dx * dx + dy * dy;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the difference between two coordinates as a new
|
||||
* goog.math.Coordinate.
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @param {!goog.math.Coordinate} b A Coordinate.
|
||||
* @return {!goog.math.Coordinate} A Coordinate representing the difference
|
||||
* between `a` and `b`.
|
||||
*/
|
||||
goog.math.Coordinate.difference = function(a, b) {
|
||||
'use strict';
|
||||
return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sum of two coordinates as a new goog.math.Coordinate.
|
||||
* @param {!goog.math.Coordinate} a A Coordinate.
|
||||
* @param {!goog.math.Coordinate} b A Coordinate.
|
||||
* @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
|
||||
* coordinates.
|
||||
*/
|
||||
goog.math.Coordinate.sum = function(a, b) {
|
||||
'use strict';
|
||||
return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rounds the x and y fields to the next larger integer values.
|
||||
* @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.ceil = function() {
|
||||
'use strict';
|
||||
this.x = Math.ceil(this.x);
|
||||
this.y = Math.ceil(this.y);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rounds the x and y fields to the next smaller integer values.
|
||||
* @return {!goog.math.Coordinate} This coordinate with floored fields.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.floor = function() {
|
||||
'use strict';
|
||||
this.x = Math.floor(this.x);
|
||||
this.y = Math.floor(this.y);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rounds the x and y fields to the nearest integer values.
|
||||
* @return {!goog.math.Coordinate} This coordinate with rounded fields.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.round = function() {
|
||||
'use strict';
|
||||
this.x = Math.round(this.x);
|
||||
this.y = Math.round(this.y);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Translates this box by the given offsets. If a `goog.math.Coordinate`
|
||||
* is given, then the x and y values are translated by the coordinate's x and y.
|
||||
* Otherwise, x and y are translated by `tx` and `opt_ty`
|
||||
* respectively.
|
||||
* @param {number|goog.math.Coordinate} tx The value to translate x by or the
|
||||
* the coordinate to translate this coordinate by.
|
||||
* @param {number=} opt_ty The value to translate y by.
|
||||
* @return {!goog.math.Coordinate} This coordinate after translating.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
|
||||
'use strict';
|
||||
if (tx instanceof goog.math.Coordinate) {
|
||||
this.x += tx.x;
|
||||
this.y += tx.y;
|
||||
} else {
|
||||
this.x += Number(tx);
|
||||
if (typeof opt_ty === 'number') {
|
||||
this.y += opt_ty;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scales this coordinate by the given scale factors. The x and y values are
|
||||
* scaled by `sx` and `opt_sy` respectively. If `opt_sy`
|
||||
* is not given, then `sx` is used for both x and y.
|
||||
* @param {number} sx The scale factor to use for the x dimension.
|
||||
* @param {number=} opt_sy The scale factor to use for the y dimension.
|
||||
* @return {!goog.math.Coordinate} This coordinate after scaling.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
|
||||
'use strict';
|
||||
var sy = (typeof opt_sy === 'number') ? opt_sy : sx;
|
||||
this.x *= sx;
|
||||
this.y *= sy;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rotates this coordinate clockwise about the origin (or, optionally, the given
|
||||
* center) by the given angle, in radians.
|
||||
* @param {number} radians The angle by which to rotate this coordinate
|
||||
* clockwise about the given center, in radians.
|
||||
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
|
||||
* to (0, 0) if not given.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
|
||||
'use strict';
|
||||
var center = opt_center || new goog.math.Coordinate(0, 0);
|
||||
|
||||
var x = this.x;
|
||||
var y = this.y;
|
||||
var cos = Math.cos(radians);
|
||||
var sin = Math.sin(radians);
|
||||
|
||||
this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
|
||||
this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rotates this coordinate clockwise about the origin (or, optionally, the given
|
||||
* center) by the given angle, in degrees.
|
||||
* @param {number} degrees The angle by which to rotate this coordinate
|
||||
* clockwise about the given center, in degrees.
|
||||
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
|
||||
* to (0, 0) if not given.
|
||||
*/
|
||||
goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
|
||||
'use strict';
|
||||
this.rotateRadians(goog.math.toRadians(degrees), opt_center);
|
||||
};
|
||||
Executable
+892
@@ -0,0 +1,892 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines an Integer class for representing (potentially)
|
||||
* infinite length two's-complement integer values.
|
||||
*
|
||||
* For the specific case of 64-bit integers, use goog.math.Long, which is more
|
||||
* efficient.
|
||||
*/
|
||||
|
||||
goog.provide('goog.math.Integer');
|
||||
|
||||
goog.require('goog.reflect');
|
||||
|
||||
/**
|
||||
* Constructs a two's-complement integer an array containing bits of the
|
||||
* integer in 32-bit (signed) pieces, given in little-endian order (i.e.,
|
||||
* lowest-order bits in the first piece), and the sign of -1 or 0.
|
||||
*
|
||||
* See the from* functions below for other convenient ways of constructing
|
||||
* Integers.
|
||||
*
|
||||
* The internal representation of an integer is an array of 32-bit signed
|
||||
* pieces, along with a sign (0 or -1) that indicates the contents of all the
|
||||
* other 32-bit pieces out to infinity. We use 32-bit pieces because these are
|
||||
* the size of integers on which JavaScript performs bit-operations. For
|
||||
* operations like addition and multiplication, we split each number into 16-bit
|
||||
* pieces, which can easily be multiplied within JavaScript's floating-point
|
||||
* representation without overflow or change in sign.
|
||||
*
|
||||
* @struct
|
||||
* @constructor
|
||||
* @param {Array<number>} bits Array containing the bits of the number.
|
||||
* @param {number} sign The sign of the number: -1 for negative and 0 positive.
|
||||
* @final
|
||||
*/
|
||||
goog.math.Integer = function(bits, sign) {
|
||||
'use strict';
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.sign_ = sign;
|
||||
|
||||
// Note: using a local variable while initializing the array helps the
|
||||
// compiler understand that assigning to the array is local side-effect and
|
||||
// that enables the entire constructor to be seen as side-effect free.
|
||||
var localBits = [];
|
||||
|
||||
// Copy the 32-bit signed integer values passed in. We prune out those at the
|
||||
// top that equal the sign since they are redundant.
|
||||
var top = true;
|
||||
|
||||
for (var i = bits.length - 1; i >= 0; i--) {
|
||||
var val = bits[i] | 0;
|
||||
if (!top || val != sign) {
|
||||
localBits[i] = val;
|
||||
top = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {!Array<number>}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
this.bits_ = localBits;
|
||||
};
|
||||
|
||||
|
||||
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
|
||||
// from* methods on which they depend.
|
||||
|
||||
|
||||
/**
|
||||
* A cache of the Integer representations of small integer values.
|
||||
* @type {!Object<number, !goog.math.Integer>}
|
||||
* @private
|
||||
*/
|
||||
goog.math.Integer.IntCache_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an Integer representing the given (32-bit) integer value.
|
||||
* @param {number} value A 32-bit integer value.
|
||||
* @return {!goog.math.Integer} The corresponding Integer value.
|
||||
*/
|
||||
goog.math.Integer.fromInt = function(value) {
|
||||
'use strict';
|
||||
if (-128 <= value && value < 128) {
|
||||
return goog.reflect.cache(
|
||||
goog.math.Integer.IntCache_, value, function(val) {
|
||||
'use strict';
|
||||
return new goog.math.Integer([val | 0], val < 0 ? -1 : 0);
|
||||
});
|
||||
}
|
||||
return new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an Integer representing the given value, provided that it is a finite
|
||||
* number. Otherwise, zero is returned.
|
||||
* @param {number} value The value in question.
|
||||
* @return {!goog.math.Integer} The corresponding Integer value.
|
||||
*/
|
||||
goog.math.Integer.fromNumber = function(value) {
|
||||
'use strict';
|
||||
if (isNaN(value) || !isFinite(value)) {
|
||||
return goog.math.Integer.ZERO;
|
||||
} else if (value < 0) {
|
||||
return goog.math.Integer.fromNumber(-value).negate();
|
||||
} else {
|
||||
var bits = [];
|
||||
var pow = 1;
|
||||
for (var i = 0; value >= pow; i++) {
|
||||
bits[i] = (value / pow) | 0;
|
||||
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
|
||||
}
|
||||
return new goog.math.Integer(bits, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a Integer representing the value that comes by concatenating the
|
||||
* given entries, each is assumed to be 32 signed bits, given in little-endian
|
||||
* order (lowest order bits in the lowest index), and sign-extending the highest
|
||||
* order 32-bit value.
|
||||
* @param {Array<number>} bits The bits of the number, in 32-bit signed pieces,
|
||||
* in little-endian order.
|
||||
* @return {!goog.math.Integer} The corresponding Integer value.
|
||||
*/
|
||||
goog.math.Integer.fromBits = function(bits) {
|
||||
'use strict';
|
||||
var high = bits[bits.length - 1];
|
||||
return new goog.math.Integer(bits, high & (1 << 31) ? -1 : 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an Integer representation of the given string, written using the
|
||||
* given radix.
|
||||
* @param {string} str The textual representation of the Integer.
|
||||
* @param {number=} opt_radix The radix in which the text is written.
|
||||
* @return {!goog.math.Integer} The corresponding Integer value.
|
||||
*/
|
||||
goog.math.Integer.fromString = function(str, opt_radix) {
|
||||
'use strict';
|
||||
if (str.length == 0) {
|
||||
throw new Error('number format error: empty string');
|
||||
}
|
||||
|
||||
var radix = opt_radix || 10;
|
||||
if (radix < 2 || 36 < radix) {
|
||||
throw new Error('radix out of range: ' + radix);
|
||||
}
|
||||
|
||||
if (str.charAt(0) == '-') {
|
||||
return goog.math.Integer.fromString(str.substring(1), radix).negate();
|
||||
} else if (str.indexOf('-') >= 0) {
|
||||
throw new Error('number format error: interior "-" character');
|
||||
}
|
||||
|
||||
// Do several (8) digits each time through the loop, so as to
|
||||
// minimize the calls to the very expensive emulated div.
|
||||
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 8));
|
||||
|
||||
var result = goog.math.Integer.ZERO;
|
||||
for (var i = 0; i < str.length; i += 8) {
|
||||
var size = Math.min(8, str.length - i);
|
||||
var value = parseInt(str.substring(i, i + size), radix);
|
||||
if (size < 8) {
|
||||
var power = goog.math.Integer.fromNumber(Math.pow(radix, size));
|
||||
result = result.multiply(power).add(goog.math.Integer.fromNumber(value));
|
||||
} else {
|
||||
result = result.multiply(radixToPower);
|
||||
result = result.add(goog.math.Integer.fromNumber(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A number used repeatedly in calculations. This must appear before the first
|
||||
* call to the from* functions below.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.math.Integer.TWO_PWR_32_DBL_ = (1 << 16) * (1 << 16);
|
||||
|
||||
|
||||
/** @type {!goog.math.Integer} */
|
||||
goog.math.Integer.ZERO = goog.math.Integer.fromInt(0);
|
||||
|
||||
/** @type {!goog.math.Integer} */
|
||||
goog.math.Integer.ONE = goog.math.Integer.fromInt(1);
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @type {!goog.math.Integer}
|
||||
* @private
|
||||
*/
|
||||
goog.math.Integer.TWO_PWR_24_ = goog.math.Integer.fromInt(1 << 24);
|
||||
|
||||
/**
|
||||
* Returns the value, assuming it is a 32-bit integer.
|
||||
* @return {number} The corresponding int value.
|
||||
*/
|
||||
goog.math.Integer.prototype.toInt = function() {
|
||||
'use strict';
|
||||
return this.bits_.length > 0 ? this.bits_[0] : this.sign_;
|
||||
};
|
||||
|
||||
|
||||
/** @return {number} The closest floating-point representation to this value. */
|
||||
goog.math.Integer.prototype.toNumber = function() {
|
||||
'use strict';
|
||||
if (this.isNegative()) {
|
||||
return -this.negate().toNumber();
|
||||
} else {
|
||||
var val = 0;
|
||||
var pow = 1;
|
||||
for (var i = 0; i < this.bits_.length; i++) {
|
||||
val += this.getBitsUnsigned(i) * pow;
|
||||
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number=} opt_radix The radix in which the text should be written.
|
||||
* @return {string} The textual representation of this value.
|
||||
* @override
|
||||
*/
|
||||
goog.math.Integer.prototype.toString = function(opt_radix) {
|
||||
'use strict';
|
||||
var radix = opt_radix || 10;
|
||||
if (radix < 2 || 36 < radix) {
|
||||
throw new Error('radix out of range: ' + radix);
|
||||
}
|
||||
|
||||
if (this.isZero()) {
|
||||
return '0';
|
||||
} else if (this.isNegative()) {
|
||||
return '-' + this.negate().toString(radix);
|
||||
}
|
||||
|
||||
// Do several (6) digits each time through the loop, so as to
|
||||
// minimize the calls to the very expensive emulated div.
|
||||
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 6));
|
||||
|
||||
var rem = this;
|
||||
var result = '';
|
||||
while (true) {
|
||||
var remDiv = rem.divide(radixToPower);
|
||||
// The right shifting fixes negative values in the case when
|
||||
// intval >= 2^31; for more details see
|
||||
// https://github.com/google/closure-library/pull/498
|
||||
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() >>> 0;
|
||||
var digits = intval.toString(radix);
|
||||
|
||||
rem = remDiv;
|
||||
if (rem.isZero()) {
|
||||
return digits + result;
|
||||
} else {
|
||||
while (digits.length < 6) {
|
||||
digits = '0' + digits;
|
||||
}
|
||||
result = '' + digits + result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index-th 32-bit (signed) piece of the Integer according to
|
||||
* little-endian order (i.e., index 0 contains the smallest bits).
|
||||
* @param {number} index The index in question.
|
||||
* @return {number} The requested 32-bits as a signed number.
|
||||
*/
|
||||
goog.math.Integer.prototype.getBits = function(index) {
|
||||
'use strict';
|
||||
if (index < 0) {
|
||||
return 0; // Allowing this simplifies bit shifting operations below...
|
||||
} else if (index < this.bits_.length) {
|
||||
return this.bits_[index];
|
||||
} else {
|
||||
return this.sign_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index-th 32-bit piece as an unsigned number.
|
||||
* @param {number} index The index in question.
|
||||
* @return {number} The requested 32-bits as an unsigned number.
|
||||
*/
|
||||
goog.math.Integer.prototype.getBitsUnsigned = function(index) {
|
||||
'use strict';
|
||||
var val = this.getBits(index);
|
||||
return val >= 0 ? val : goog.math.Integer.TWO_PWR_32_DBL_ + val;
|
||||
};
|
||||
|
||||
|
||||
/** @return {number} The sign bit of this number, -1 or 0. */
|
||||
goog.math.Integer.prototype.getSign = function() {
|
||||
'use strict';
|
||||
return this.sign_;
|
||||
};
|
||||
|
||||
|
||||
/** @return {boolean} Whether this value is zero. */
|
||||
goog.math.Integer.prototype.isZero = function() {
|
||||
'use strict';
|
||||
if (this.sign_ != 0) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < this.bits_.length; i++) {
|
||||
if (this.bits_[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/** @return {boolean} Whether this value is negative. */
|
||||
goog.math.Integer.prototype.isNegative = function() {
|
||||
'use strict';
|
||||
return this.sign_ == -1;
|
||||
};
|
||||
|
||||
|
||||
/** @return {boolean} Whether this value is odd. */
|
||||
goog.math.Integer.prototype.isOdd = function() {
|
||||
'use strict';
|
||||
return (this.bits_.length == 0) && (this.sign_ == -1) ||
|
||||
(this.bits_.length > 0) && ((this.bits_[0] & 1) != 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer equals the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.equals = function(other) {
|
||||
'use strict';
|
||||
if (this.sign_ != other.sign_) {
|
||||
return false;
|
||||
}
|
||||
var len = Math.max(this.bits_.length, other.bits_.length);
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (this.getBits(i) != other.getBits(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer does not equal the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.notEquals = function(other) {
|
||||
'use strict';
|
||||
return !this.equals(other);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer is greater than the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.greaterThan = function(other) {
|
||||
'use strict';
|
||||
return this.compare(other) > 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer is greater than or equal to the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.greaterThanOrEqual = function(other) {
|
||||
'use strict';
|
||||
return this.compare(other) >= 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer is less than the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.lessThan = function(other) {
|
||||
'use strict';
|
||||
return this.compare(other) < 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {boolean} Whether this Integer is less than or equal to the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.lessThanOrEqual = function(other) {
|
||||
'use strict';
|
||||
return this.compare(other) <= 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares this Integer with the given one.
|
||||
* @param {goog.math.Integer} other Integer to compare against.
|
||||
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
|
||||
* if the given one is greater.
|
||||
*/
|
||||
goog.math.Integer.prototype.compare = function(other) {
|
||||
'use strict';
|
||||
var diff = this.subtract(other);
|
||||
if (diff.isNegative()) {
|
||||
return -1;
|
||||
} else if (diff.isZero()) {
|
||||
return 0;
|
||||
} else {
|
||||
return +1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an integer with only the first numBits bits of this value, sign
|
||||
* extended from the final bit.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!goog.math.Integer} The shorted integer value.
|
||||
*/
|
||||
goog.math.Integer.prototype.shorten = function(numBits) {
|
||||
'use strict';
|
||||
var arr_index = (numBits - 1) >> 5;
|
||||
var bit_index = (numBits - 1) % 32;
|
||||
var bits = [];
|
||||
for (var i = 0; i < arr_index; i++) {
|
||||
bits[i] = this.getBits(i);
|
||||
}
|
||||
var sigBits = bit_index == 31 ? 0xFFFFFFFF : (1 << (bit_index + 1)) - 1;
|
||||
var val = this.getBits(arr_index) & sigBits;
|
||||
if (val & (1 << bit_index)) {
|
||||
val |= 0xFFFFFFFF - sigBits;
|
||||
bits[arr_index] = val;
|
||||
return new goog.math.Integer(bits, -1);
|
||||
} else {
|
||||
bits[arr_index] = val;
|
||||
return new goog.math.Integer(bits, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @return {!goog.math.Integer} The negation of this value. */
|
||||
goog.math.Integer.prototype.negate = function() {
|
||||
'use strict';
|
||||
return this.not().add(goog.math.Integer.ONE);
|
||||
};
|
||||
|
||||
|
||||
/** @return {!goog.math.Integer} The absolute value of this value. */
|
||||
goog.math.Integer.prototype.abs = function() {
|
||||
'use strict';
|
||||
return this.isNegative() ? this.negate() : this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sum of this and the given Integer.
|
||||
* @param {goog.math.Integer} other The Integer to add to this.
|
||||
* @return {!goog.math.Integer} The Integer result.
|
||||
*/
|
||||
goog.math.Integer.prototype.add = function(other) {
|
||||
'use strict';
|
||||
var len = Math.max(this.bits_.length, other.bits_.length);
|
||||
var arr = [];
|
||||
var carry = 0;
|
||||
|
||||
for (var i = 0; i <= len; i++) {
|
||||
var a1 = this.getBits(i) >>> 16;
|
||||
var a0 = this.getBits(i) & 0xFFFF;
|
||||
|
||||
var b1 = other.getBits(i) >>> 16;
|
||||
var b0 = other.getBits(i) & 0xFFFF;
|
||||
|
||||
var c0 = carry + a0 + b0;
|
||||
var c1 = (c0 >>> 16) + a1 + b1;
|
||||
carry = c1 >>> 16;
|
||||
c0 &= 0xFFFF;
|
||||
c1 &= 0xFFFF;
|
||||
arr[i] = (c1 << 16) | c0;
|
||||
}
|
||||
return goog.math.Integer.fromBits(arr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the difference of this and the given Integer.
|
||||
* @param {goog.math.Integer} other The Integer to subtract from this.
|
||||
* @return {!goog.math.Integer} The Integer result.
|
||||
*/
|
||||
goog.math.Integer.prototype.subtract = function(other) {
|
||||
'use strict';
|
||||
return this.add(other.negate());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the product of this and the given Integer.
|
||||
* @param {goog.math.Integer} other The Integer to multiply against this.
|
||||
* @return {!goog.math.Integer} The product of this and the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.multiply = function(other) {
|
||||
'use strict';
|
||||
if (this.isZero()) {
|
||||
return goog.math.Integer.ZERO;
|
||||
} else if (other.isZero()) {
|
||||
return goog.math.Integer.ZERO;
|
||||
}
|
||||
|
||||
if (this.isNegative()) {
|
||||
if (other.isNegative()) {
|
||||
return this.negate().multiply(other.negate());
|
||||
} else {
|
||||
return this.negate().multiply(other).negate();
|
||||
}
|
||||
} else if (other.isNegative()) {
|
||||
return this.multiply(other.negate()).negate();
|
||||
}
|
||||
|
||||
// If both numbers are small, use float multiplication
|
||||
if (this.lessThan(goog.math.Integer.TWO_PWR_24_) &&
|
||||
other.lessThan(goog.math.Integer.TWO_PWR_24_)) {
|
||||
return goog.math.Integer.fromNumber(this.toNumber() * other.toNumber());
|
||||
}
|
||||
|
||||
// Fill in an array of 16-bit products.
|
||||
var len = this.bits_.length + other.bits_.length;
|
||||
var arr = [];
|
||||
for (var i = 0; i < 2 * len; i++) {
|
||||
arr[i] = 0;
|
||||
}
|
||||
for (var i = 0; i < this.bits_.length; i++) {
|
||||
for (var j = 0; j < other.bits_.length; j++) {
|
||||
var a1 = this.getBits(i) >>> 16;
|
||||
var a0 = this.getBits(i) & 0xFFFF;
|
||||
|
||||
var b1 = other.getBits(j) >>> 16;
|
||||
var b0 = other.getBits(j) & 0xFFFF;
|
||||
|
||||
arr[2 * i + 2 * j] += a0 * b0;
|
||||
goog.math.Integer.carry16_(arr, 2 * i + 2 * j);
|
||||
arr[2 * i + 2 * j + 1] += a1 * b0;
|
||||
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
|
||||
arr[2 * i + 2 * j + 1] += a0 * b1;
|
||||
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
|
||||
arr[2 * i + 2 * j + 2] += a1 * b1;
|
||||
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the 16-bit values into 32-bit values.
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = (arr[2 * i + 1] << 16) | arr[2 * i];
|
||||
}
|
||||
for (var i = len; i < 2 * len; i++) {
|
||||
arr[i] = 0;
|
||||
}
|
||||
return new goog.math.Integer(arr, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Carries any overflow from the given index into later entries.
|
||||
* @param {Array<number>} bits Array of 16-bit values in little-endian order.
|
||||
* @param {number} index The index in question.
|
||||
* @private
|
||||
*/
|
||||
goog.math.Integer.carry16_ = function(bits, index) {
|
||||
'use strict';
|
||||
while ((bits[index] & 0xFFFF) != bits[index]) {
|
||||
bits[index + 1] += bits[index] >>> 16;
|
||||
bits[index] &= 0xFFFF;
|
||||
index++;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns "this" Integer divided by the given one. Both "this" and the given
|
||||
* Integer MUST be positive.
|
||||
*
|
||||
* This method is only needed for very large numbers (>10^308),
|
||||
* for which the original division algorithm gets into an infinite
|
||||
* loop (see https://github.com/google/closure-library/issues/500).
|
||||
*
|
||||
* The algorithm has some possible performance enhancements (or
|
||||
* could be rewritten entirely), it's just an initial solution for
|
||||
* the issue linked above.
|
||||
*
|
||||
* @param {!goog.math.Integer} other The Integer to divide "this" by.
|
||||
* @return {!goog.math.Integer.DivisionResult}
|
||||
* @private
|
||||
*/
|
||||
goog.math.Integer.prototype.slowDivide_ = function(other) {
|
||||
'use strict';
|
||||
if (this.isNegative() || other.isNegative()) {
|
||||
throw new Error('slowDivide_ only works with positive integers.');
|
||||
}
|
||||
|
||||
var twoPower = goog.math.Integer.ONE;
|
||||
var multiple = other;
|
||||
|
||||
// First we have to figure out what the highest bit of the result
|
||||
// is, so we increase "twoPower" and "multiple" until "multiple"
|
||||
// exceeds "this".
|
||||
while (multiple.lessThanOrEqual(this)) {
|
||||
twoPower = twoPower.shiftLeft(1);
|
||||
multiple = multiple.shiftLeft(1);
|
||||
}
|
||||
|
||||
// Rewind by one power of two, giving us the highest bit of the
|
||||
// result.
|
||||
var res = twoPower.shiftRight(1);
|
||||
var total = multiple.shiftRight(1);
|
||||
|
||||
// Now we starting decreasing "multiple" and "twoPower" to find the
|
||||
// rest of the bits of the result.
|
||||
var total2;
|
||||
multiple = multiple.shiftRight(2);
|
||||
twoPower = twoPower.shiftRight(2);
|
||||
while (!multiple.isZero()) {
|
||||
// whenever we can add "multiple" to the total and not exceed
|
||||
// "this", that means we've found a 1 bit. Else we've found a 0
|
||||
// and don't need to add to the result.
|
||||
total2 = total.add(multiple);
|
||||
if (total2.lessThanOrEqual(this)) {
|
||||
res = res.add(twoPower);
|
||||
total = total2;
|
||||
}
|
||||
multiple = multiple.shiftRight(1);
|
||||
twoPower = twoPower.shiftRight(1);
|
||||
}
|
||||
|
||||
|
||||
// TODO(user): Calculate this more efficiently during the division.
|
||||
// This is kind of a waste since it isn't always needed, but it keeps the
|
||||
// API smooth. Since this is already a slow path it probably isn't a big deal.
|
||||
var remainder = this.subtract(res.multiply(other));
|
||||
return new goog.math.Integer.DivisionResult(res, remainder);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this Integer divided by the given one.
|
||||
* @param {!goog.math.Integer} other The Integer to divide this by.
|
||||
* @return {!goog.math.Integer} This value divided by the given one.
|
||||
*/
|
||||
goog.math.Integer.prototype.divide = function(other) {
|
||||
'use strict';
|
||||
return this.divideAndRemainder(other).quotient;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A struct for holding the quotient and remainder of a division.
|
||||
*
|
||||
* @constructor
|
||||
* @final
|
||||
* @struct
|
||||
*
|
||||
* @param {!goog.math.Integer} quotient
|
||||
* @param {!goog.math.Integer} remainder
|
||||
*/
|
||||
goog.math.Integer.DivisionResult = function(quotient, remainder) {
|
||||
'use strict';
|
||||
/** @const */
|
||||
this.quotient = quotient;
|
||||
|
||||
/** @const */
|
||||
this.remainder = remainder;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this Integer divided by the given one, as well as the remainder of
|
||||
* that division.
|
||||
*
|
||||
* @param {!goog.math.Integer} other The Integer to divide this by.
|
||||
* @return {!goog.math.Integer.DivisionResult}
|
||||
*/
|
||||
goog.math.Integer.prototype.divideAndRemainder = function(other) {
|
||||
'use strict';
|
||||
if (other.isZero()) {
|
||||
throw new Error('division by zero');
|
||||
} else if (this.isZero()) {
|
||||
return new goog.math.Integer.DivisionResult(
|
||||
goog.math.Integer.ZERO, goog.math.Integer.ZERO);
|
||||
}
|
||||
|
||||
if (this.isNegative()) {
|
||||
// Do the division on the negative of the numerator...
|
||||
var result = this.negate().divideAndRemainder(other);
|
||||
return new goog.math.Integer.DivisionResult(
|
||||
// ...and flip the sign back after.
|
||||
result.quotient.negate(),
|
||||
// The remainder must always have the same sign as the numerator.
|
||||
result.remainder.negate());
|
||||
} else if (other.isNegative()) {
|
||||
// Do the division on the negative of the denominator...
|
||||
var result = this.divideAndRemainder(other.negate());
|
||||
return new goog.math.Integer.DivisionResult(
|
||||
// ...and flip the sign back after.
|
||||
result.quotient.negate(),
|
||||
// The remainder must always have the same sign as the numerator.
|
||||
result.remainder);
|
||||
}
|
||||
|
||||
// Have to degrade to slowDivide for Very Large Numbers, because
|
||||
// they're out of range for the floating-point approximation
|
||||
// technique used below.
|
||||
if (this.bits_.length > 30) {
|
||||
return this.slowDivide_(other);
|
||||
}
|
||||
|
||||
// Repeat the following until the remainder is less than other: find a
|
||||
// floating-point that approximates remainder / other *from below*, add this
|
||||
// into the result, and subtract it from the remainder. It is critical that
|
||||
// the approximate value is less than or equal to the real value so that the
|
||||
// remainder never becomes negative.
|
||||
var res = goog.math.Integer.ZERO;
|
||||
var rem = this;
|
||||
while (rem.greaterThanOrEqual(other)) {
|
||||
// Approximate the result of division. This may be a little greater or
|
||||
// smaller than the actual value.
|
||||
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
|
||||
|
||||
// We will tweak the approximate result by changing it in the 48-th digit or
|
||||
// the smallest non-fractional digit, whichever is larger.
|
||||
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
|
||||
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
|
||||
|
||||
// Decrease the approximation until it is smaller than the remainder. Note
|
||||
// that if it is too large, the product overflows and is negative.
|
||||
var approxRes = goog.math.Integer.fromNumber(approx);
|
||||
var approxRem = approxRes.multiply(other);
|
||||
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
|
||||
approx -= delta;
|
||||
approxRes = goog.math.Integer.fromNumber(approx);
|
||||
approxRem = approxRes.multiply(other);
|
||||
}
|
||||
|
||||
// We know the answer can't be zero... and actually, zero would cause
|
||||
// infinite recursion since we would make no progress.
|
||||
if (approxRes.isZero()) {
|
||||
approxRes = goog.math.Integer.ONE;
|
||||
}
|
||||
|
||||
res = res.add(approxRes);
|
||||
rem = rem.subtract(approxRem);
|
||||
}
|
||||
return new goog.math.Integer.DivisionResult(res, rem);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this Integer modulo the given one.
|
||||
* @param {!goog.math.Integer} other The Integer by which to mod.
|
||||
* @return {!goog.math.Integer} This value modulo the given one.
|
||||
*/
|
||||
goog.math.Integer.prototype.modulo = function(other) {
|
||||
'use strict';
|
||||
return this.divideAndRemainder(other).remainder;
|
||||
};
|
||||
|
||||
|
||||
/** @return {!goog.math.Integer} The bitwise-NOT of this value. */
|
||||
goog.math.Integer.prototype.not = function() {
|
||||
'use strict';
|
||||
var len = this.bits_.length;
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = ~this.bits_[i];
|
||||
}
|
||||
return new goog.math.Integer(arr, ~this.sign_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bitwise-AND of this Integer and the given one.
|
||||
* @param {goog.math.Integer} other The Integer to AND with this.
|
||||
* @return {!goog.math.Integer} The bitwise-AND of this and the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.and = function(other) {
|
||||
'use strict';
|
||||
var len = Math.max(this.bits_.length, other.bits_.length);
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = this.getBits(i) & other.getBits(i);
|
||||
}
|
||||
return new goog.math.Integer(arr, this.sign_ & other.sign_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bitwise-OR of this Integer and the given one.
|
||||
* @param {goog.math.Integer} other The Integer to OR with this.
|
||||
* @return {!goog.math.Integer} The bitwise-OR of this and the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.or = function(other) {
|
||||
'use strict';
|
||||
var len = Math.max(this.bits_.length, other.bits_.length);
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = this.getBits(i) | other.getBits(i);
|
||||
}
|
||||
return new goog.math.Integer(arr, this.sign_ | other.sign_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the bitwise-XOR of this Integer and the given one.
|
||||
* @param {goog.math.Integer} other The Integer to XOR with this.
|
||||
* @return {!goog.math.Integer} The bitwise-XOR of this and the other.
|
||||
*/
|
||||
goog.math.Integer.prototype.xor = function(other) {
|
||||
'use strict';
|
||||
var len = Math.max(this.bits_.length, other.bits_.length);
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
arr[i] = this.getBits(i) ^ other.getBits(i);
|
||||
}
|
||||
return new goog.math.Integer(arr, this.sign_ ^ other.sign_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this value with bits shifted to the left by the given amount.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!goog.math.Integer} This shifted to the left by the given amount.
|
||||
*/
|
||||
goog.math.Integer.prototype.shiftLeft = function(numBits) {
|
||||
'use strict';
|
||||
var arr_delta = numBits >> 5;
|
||||
var bit_delta = numBits % 32;
|
||||
var len = this.bits_.length + arr_delta + (bit_delta > 0 ? 1 : 0);
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (bit_delta > 0) {
|
||||
arr[i] = (this.getBits(i - arr_delta) << bit_delta) |
|
||||
(this.getBits(i - arr_delta - 1) >>> (32 - bit_delta));
|
||||
} else {
|
||||
arr[i] = this.getBits(i - arr_delta);
|
||||
}
|
||||
}
|
||||
return new goog.math.Integer(arr, this.sign_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns this value with bits shifted to the right by the given amount.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!goog.math.Integer} This shifted to the right by the given amount.
|
||||
*/
|
||||
goog.math.Integer.prototype.shiftRight = function(numBits) {
|
||||
'use strict';
|
||||
var arr_delta = numBits >> 5;
|
||||
var bit_delta = numBits % 32;
|
||||
var len = this.bits_.length - arr_delta;
|
||||
var arr = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (bit_delta > 0) {
|
||||
arr[i] = (this.getBits(i + arr_delta) >>> bit_delta) |
|
||||
(this.getBits(i + arr_delta + 1) << (32 - bit_delta));
|
||||
} else {
|
||||
arr[i] = this.getBits(i + arr_delta);
|
||||
}
|
||||
}
|
||||
return new goog.math.Integer(arr, this.sign_);
|
||||
};
|
||||
Executable
+903
@@ -0,0 +1,903 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines a Long class for representing a 64-bit two's-complement
|
||||
* integer value, which faithfully simulates the behavior of a Java "long". This
|
||||
* implementation is derived from LongLib in GWT.
|
||||
*/
|
||||
|
||||
goog.module('goog.math.Long');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const asserts = goog.require('goog.asserts');
|
||||
const reflect = goog.require('goog.reflect');
|
||||
|
||||
/**
|
||||
* Represents a 64-bit two's-complement integer, given its low and high 32-bit
|
||||
* values as *signed* integers. See the from* functions below for more
|
||||
* convenient ways of constructing Longs.
|
||||
*
|
||||
* The internal representation of a long is the two given signed, 32-bit values.
|
||||
* We use 32-bit pieces because these are the size of integers on which
|
||||
* JavaScript performs bit-operations. For operations like addition and
|
||||
* multiplication, we split each number into 16-bit pieces, which can easily be
|
||||
* multiplied within JavaScript's floating-point representation without overflow
|
||||
* or change in sign.
|
||||
*
|
||||
* In the algorithms below, we frequently reduce the negative case to the
|
||||
* positive case by negating the input(s) and then post-processing the result.
|
||||
* Note that we must ALWAYS check specially whether those values are MIN_VALUE
|
||||
* (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
|
||||
* a positive number, it overflows back into a negative). Not handling this
|
||||
* case would often result in infinite recursion.
|
||||
* @final
|
||||
*/
|
||||
class Long {
|
||||
/**
|
||||
* @param {number} low The low (signed) 32 bits of the long.
|
||||
* @param {number} high The high (signed) 32 bits of the long.
|
||||
*/
|
||||
constructor(low, high) {
|
||||
/**
|
||||
* @const {number}
|
||||
* @private
|
||||
*/
|
||||
this.low_ = low | 0; // force into 32 signed bits.
|
||||
|
||||
/**
|
||||
* @const {number}
|
||||
* @private
|
||||
*/
|
||||
this.high_ = high | 0; // force into 32 signed bits.
|
||||
}
|
||||
|
||||
/** @return {number} The value, assuming it is a 32-bit integer. */
|
||||
toInt() {
|
||||
return this.low_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} The closest floating-point representation to this value.
|
||||
*/
|
||||
toNumber() {
|
||||
return this.high_ * TWO_PWR_32_DBL_ + this.getLowBitsUnsigned();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} if can be exactly represented using number (i.e.
|
||||
* abs(value) < 2^53).
|
||||
*/
|
||||
isSafeInteger() {
|
||||
var top11Bits = this.high_ >> 21;
|
||||
// If top11Bits are all 0s, then the number is between [0, 2^53-1]
|
||||
return top11Bits == 0
|
||||
// If top11Bits are all 1s, then the number is between [-1, -2^53]
|
||||
|| (top11Bits == -1
|
||||
// and exclude -2^53
|
||||
&& !(this.low_ == 0 && this.high_ == (0xffe00000 | 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number=} opt_radix The radix in which the text should be written.
|
||||
* @return {string} The textual representation of this value.
|
||||
* @override
|
||||
*/
|
||||
toString(opt_radix) {
|
||||
var radix = opt_radix || 10;
|
||||
if (radix < 2 || 36 < radix) {
|
||||
throw new Error('radix out of range: ' + radix);
|
||||
}
|
||||
|
||||
// We can avoid very expensive division based code path for some common
|
||||
// cases.
|
||||
if (this.isSafeInteger()) {
|
||||
var asNumber = this.toNumber();
|
||||
// Shortcutting for radix 10 (common case) to avoid boxing via toString:
|
||||
// https://jsperf.com/tostring-vs-vs-if
|
||||
return radix == 10 ? ('' + asNumber) : asNumber.toString(radix);
|
||||
}
|
||||
|
||||
// We need to split 64bit integer into: `a * radix**safeDigits + b` where
|
||||
// neither `a` nor `b` exceeds 53 bits, meaning that safeDigits can be any
|
||||
// number in a range: [(63 - 53) / log2(radix); 53 / log2(radix)].
|
||||
|
||||
// Other options that need to be benchmarked:
|
||||
// 11..16 - (radix >> 2);
|
||||
// 10..13 - (radix >> 3);
|
||||
// 10..11 - (radix >> 4);
|
||||
var safeDigits = 14 - (radix >> 2);
|
||||
|
||||
var radixPowSafeDigits = Math.pow(radix, safeDigits);
|
||||
var radixToPower =
|
||||
Long.fromBits(radixPowSafeDigits, radixPowSafeDigits / TWO_PWR_32_DBL_);
|
||||
|
||||
var remDiv = this.div(radixToPower);
|
||||
var val = Math.abs(this.subtract(remDiv.multiply(radixToPower)).toNumber());
|
||||
var digits = radix == 10 ? ('' + val) : val.toString(radix);
|
||||
|
||||
if (digits.length < safeDigits) {
|
||||
// Up to 13 leading 0s we might need to insert as the greatest safeDigits
|
||||
// value is 14 (for radix 2).
|
||||
digits = '0000000000000'.substr(digits.length - safeDigits) + digits;
|
||||
}
|
||||
|
||||
val = remDiv.toNumber();
|
||||
return (radix == 10 ? val : val.toString(radix)) + digits;
|
||||
}
|
||||
|
||||
/** @return {number} The high 32-bits as a signed value. */
|
||||
getHighBits() {
|
||||
return this.high_;
|
||||
}
|
||||
|
||||
/** @return {number} The low 32-bits as a signed value. */
|
||||
getLowBits() {
|
||||
return this.low_;
|
||||
}
|
||||
|
||||
/** @return {number} The low 32-bits as an unsigned value. */
|
||||
getLowBitsUnsigned() {
|
||||
// The right shifting fixes negative values in the case when
|
||||
// intval >= 2^31; for more details see
|
||||
// https://github.com/google/closure-library/pull/498
|
||||
return this.low_ >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number} Returns the number of bits needed to represent the
|
||||
* absolute value of this Long.
|
||||
*/
|
||||
getNumBitsAbs() {
|
||||
if (this.isNegative()) {
|
||||
if (this.equals(Long.getMinValue())) {
|
||||
return 64;
|
||||
} else {
|
||||
return this.negate().getNumBitsAbs();
|
||||
}
|
||||
} else {
|
||||
var val = this.high_ != 0 ? this.high_ : this.low_;
|
||||
for (var bit = 31; bit > 0; bit--) {
|
||||
if ((val & (1 << bit)) != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this.high_ != 0 ? bit + 33 : bit + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether this value is zero. */
|
||||
isZero() {
|
||||
// Check low part first as there is high chance it's not 0.
|
||||
return this.low_ == 0 && this.high_ == 0;
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether this value is negative. */
|
||||
isNegative() {
|
||||
return this.high_ < 0;
|
||||
}
|
||||
|
||||
/** @return {boolean} Whether this value is odd. */
|
||||
isOdd() {
|
||||
return (this.low_ & 1) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code for this long object that similar java.lang.Long one.
|
||||
*
|
||||
* @return {number} 32 bit hash code for this object.
|
||||
*/
|
||||
hashCode() {
|
||||
return this.getLowBits() ^ this.getHighBits();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long equals the other.
|
||||
*/
|
||||
equals(other) {
|
||||
// Compare low parts first as there is higher chance they are different.
|
||||
return (this.low_ == other.low_) && (this.high_ == other.high_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long does not equal the other.
|
||||
*/
|
||||
notEquals(other) {
|
||||
return !this.equals(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long is less than the other.
|
||||
*/
|
||||
lessThan(other) {
|
||||
return this.compare(other) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long is less than or equal to the other.
|
||||
*/
|
||||
lessThanOrEqual(other) {
|
||||
return this.compare(other) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long is greater than the other.
|
||||
*/
|
||||
greaterThan(other) {
|
||||
return this.compare(other) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {boolean} Whether this Long is greater than or equal to the other.
|
||||
*/
|
||||
greaterThanOrEqual(other) {
|
||||
return this.compare(other) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this Long with the given one.
|
||||
* @param {?Long} other Long to compare against.
|
||||
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
|
||||
* if the given one is greater.
|
||||
*/
|
||||
compare(other) {
|
||||
if (this.high_ == other.high_) {
|
||||
if (this.low_ == other.low_) {
|
||||
return 0;
|
||||
}
|
||||
return this.getLowBitsUnsigned() > other.getLowBitsUnsigned() ? 1 : -1;
|
||||
}
|
||||
return this.high_ > other.high_ ? 1 : -1;
|
||||
}
|
||||
|
||||
/** @return {!Long} The negation of this value. */
|
||||
negate() {
|
||||
var negLow = (~this.low_ + 1) | 0;
|
||||
var overflowFromLow = !negLow;
|
||||
var negHigh = (~this.high_ + overflowFromLow) | 0;
|
||||
return Long.fromBits(negLow, negHigh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of this and the given Long.
|
||||
* @param {?Long} other Long to add to this one.
|
||||
* @return {!Long} The sum of this and the given Long.
|
||||
*/
|
||||
add(other) {
|
||||
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
|
||||
|
||||
var a48 = this.high_ >>> 16;
|
||||
var a32 = this.high_ & 0xFFFF;
|
||||
var a16 = this.low_ >>> 16;
|
||||
var a00 = this.low_ & 0xFFFF;
|
||||
|
||||
var b48 = other.high_ >>> 16;
|
||||
var b32 = other.high_ & 0xFFFF;
|
||||
var b16 = other.low_ >>> 16;
|
||||
var b00 = other.low_ & 0xFFFF;
|
||||
|
||||
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
|
||||
c00 += a00 + b00;
|
||||
c16 += c00 >>> 16;
|
||||
c00 &= 0xFFFF;
|
||||
c16 += a16 + b16;
|
||||
c32 += c16 >>> 16;
|
||||
c16 &= 0xFFFF;
|
||||
c32 += a32 + b32;
|
||||
c48 += c32 >>> 16;
|
||||
c32 &= 0xFFFF;
|
||||
c48 += a48 + b48;
|
||||
c48 &= 0xFFFF;
|
||||
return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of this and the given Long.
|
||||
* @param {?Long} other Long to subtract from this.
|
||||
* @return {!Long} The difference of this and the given Long.
|
||||
*/
|
||||
subtract(other) {
|
||||
return this.add(other.negate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product of this and the given long.
|
||||
* @param {?Long} other Long to multiply with this.
|
||||
* @return {!Long} The product of this and the other.
|
||||
*/
|
||||
multiply(other) {
|
||||
if (this.isZero()) {
|
||||
return this;
|
||||
}
|
||||
if (other.isZero()) {
|
||||
return other;
|
||||
}
|
||||
|
||||
// Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
|
||||
// We can skip products that would overflow.
|
||||
|
||||
var a48 = this.high_ >>> 16;
|
||||
var a32 = this.high_ & 0xFFFF;
|
||||
var a16 = this.low_ >>> 16;
|
||||
var a00 = this.low_ & 0xFFFF;
|
||||
|
||||
var b48 = other.high_ >>> 16;
|
||||
var b32 = other.high_ & 0xFFFF;
|
||||
var b16 = other.low_ >>> 16;
|
||||
var b00 = other.low_ & 0xFFFF;
|
||||
|
||||
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
|
||||
c00 += a00 * b00;
|
||||
c16 += c00 >>> 16;
|
||||
c00 &= 0xFFFF;
|
||||
c16 += a16 * b00;
|
||||
c32 += c16 >>> 16;
|
||||
c16 &= 0xFFFF;
|
||||
c16 += a00 * b16;
|
||||
c32 += c16 >>> 16;
|
||||
c16 &= 0xFFFF;
|
||||
c32 += a32 * b00;
|
||||
c48 += c32 >>> 16;
|
||||
c32 &= 0xFFFF;
|
||||
c32 += a16 * b16;
|
||||
c48 += c32 >>> 16;
|
||||
c32 &= 0xFFFF;
|
||||
c32 += a00 * b32;
|
||||
c48 += c32 >>> 16;
|
||||
c32 &= 0xFFFF;
|
||||
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
|
||||
c48 &= 0xFFFF;
|
||||
return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Long divided by the given one.
|
||||
* @param {?Long} other Long by which to divide.
|
||||
* @return {!Long} This Long divided by the given one.
|
||||
*/
|
||||
div(other) {
|
||||
if (other.isZero()) {
|
||||
throw new Error('division by zero');
|
||||
}
|
||||
if (this.isNegative()) {
|
||||
if (this.equals(Long.getMinValue())) {
|
||||
if (other.equals(Long.getOne()) || other.equals(Long.getNegOne())) {
|
||||
return Long.getMinValue(); // recall -MIN_VALUE == MIN_VALUE
|
||||
}
|
||||
if (other.equals(Long.getMinValue())) {
|
||||
return Long.getOne();
|
||||
}
|
||||
// At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
|
||||
var halfThis = this.shiftRight(1);
|
||||
var approx = halfThis.div(other).shiftLeft(1);
|
||||
if (approx.equals(Long.getZero())) {
|
||||
return other.isNegative() ? Long.getOne() : Long.getNegOne();
|
||||
}
|
||||
var rem = this.subtract(other.multiply(approx));
|
||||
var result = approx.add(rem.div(other));
|
||||
return result;
|
||||
}
|
||||
if (other.isNegative()) {
|
||||
return this.negate().div(other.negate());
|
||||
}
|
||||
return this.negate().div(other).negate();
|
||||
}
|
||||
if (this.isZero()) {
|
||||
return Long.getZero();
|
||||
}
|
||||
if (other.isNegative()) {
|
||||
if (other.equals(Long.getMinValue())) {
|
||||
return Long.getZero();
|
||||
}
|
||||
return this.div(other.negate()).negate();
|
||||
}
|
||||
|
||||
// Repeat the following until the remainder is less than other: find a
|
||||
// floating-point that approximates remainder / other *from below*, add this
|
||||
// into the result, and subtract it from the remainder. It is critical that
|
||||
// the approximate value is less than or equal to the real value so that the
|
||||
// remainder never becomes negative.
|
||||
var res = Long.getZero();
|
||||
var rem = this;
|
||||
while (rem.greaterThanOrEqual(other)) {
|
||||
// Approximate the result of division. This may be a little greater or
|
||||
// smaller than the actual value.
|
||||
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
|
||||
|
||||
// We will tweak the approximate result by changing it in the 48-th digit
|
||||
// or the smallest non-fractional digit, whichever is larger.
|
||||
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
|
||||
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
|
||||
|
||||
// Decrease the approximation until it is smaller than the remainder. Note
|
||||
// that if it is too large, the product overflows and is negative.
|
||||
var approxRes = Long.fromNumber(approx);
|
||||
var approxRem = approxRes.multiply(other);
|
||||
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
|
||||
approx -= delta;
|
||||
approxRes = Long.fromNumber(approx);
|
||||
approxRem = approxRes.multiply(other);
|
||||
}
|
||||
|
||||
// We know the answer can't be zero... and actually, zero would cause
|
||||
// infinite recursion since we would make no progress.
|
||||
if (approxRes.isZero()) {
|
||||
approxRes = Long.getOne();
|
||||
}
|
||||
|
||||
res = res.add(approxRes);
|
||||
rem = rem.subtract(approxRem);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Long modulo the given one.
|
||||
* @param {?Long} other Long by which to mod.
|
||||
* @return {!Long} This Long modulo the given one.
|
||||
*/
|
||||
modulo(other) {
|
||||
return this.subtract(this.div(other).multiply(other));
|
||||
}
|
||||
|
||||
/** @return {!Long} The bitwise-NOT of this value. */
|
||||
not() {
|
||||
return Long.fromBits(~this.low_, ~this.high_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitwise-AND of this Long and the given one.
|
||||
* @param {?Long} other The Long with which to AND.
|
||||
* @return {!Long} The bitwise-AND of this and the other.
|
||||
*/
|
||||
and(other) {
|
||||
return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitwise-OR of this Long and the given one.
|
||||
* @param {?Long} other The Long with which to OR.
|
||||
* @return {!Long} The bitwise-OR of this and the other.
|
||||
*/
|
||||
or(other) {
|
||||
return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitwise-XOR of this Long and the given one.
|
||||
* @param {?Long} other The Long with which to XOR.
|
||||
* @return {!Long} The bitwise-XOR of this and the other.
|
||||
*/
|
||||
xor(other) {
|
||||
return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Long with bits shifted to the left by the given amount.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!Long} This shifted to the left by the given amount.
|
||||
*/
|
||||
shiftLeft(numBits) {
|
||||
numBits &= 63;
|
||||
if (numBits == 0) {
|
||||
return this;
|
||||
} else {
|
||||
var low = this.low_;
|
||||
if (numBits < 32) {
|
||||
var high = this.high_;
|
||||
return Long.fromBits(
|
||||
low << numBits, (high << numBits) | (low >>> (32 - numBits)));
|
||||
} else {
|
||||
return Long.fromBits(0, low << (numBits - 32));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Long with bits shifted to the right by the given amount.
|
||||
* The new leading bits match the current sign bit.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!Long} This shifted to the right by the given amount.
|
||||
*/
|
||||
shiftRight(numBits) {
|
||||
numBits &= 63;
|
||||
if (numBits == 0) {
|
||||
return this;
|
||||
} else {
|
||||
var high = this.high_;
|
||||
if (numBits < 32) {
|
||||
var low = this.low_;
|
||||
return Long.fromBits(
|
||||
(low >>> numBits) | (high << (32 - numBits)), high >> numBits);
|
||||
} else {
|
||||
return Long.fromBits(high >> (numBits - 32), high >= 0 ? 0 : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this Long with bits shifted to the right by the given amount, with
|
||||
* zeros placed into the new leading bits.
|
||||
* @param {number} numBits The number of bits by which to shift.
|
||||
* @return {!Long} This shifted to the right by the given amount,
|
||||
* with zeros placed into the new leading bits.
|
||||
*/
|
||||
shiftRightUnsigned(numBits) {
|
||||
numBits &= 63;
|
||||
if (numBits == 0) {
|
||||
return this;
|
||||
} else {
|
||||
var high = this.high_;
|
||||
if (numBits < 32) {
|
||||
var low = this.low_;
|
||||
return Long.fromBits(
|
||||
(low >>> numBits) | (high << (32 - numBits)), high >>> numBits);
|
||||
} else if (numBits == 32) {
|
||||
return Long.fromBits(high, 0);
|
||||
} else {
|
||||
return Long.fromBits(high >>> (numBits - 32), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Long representing the given (32-bit) integer value.
|
||||
* @param {number} value The 32-bit integer in question.
|
||||
* @return {!Long} The corresponding Long value.
|
||||
*/
|
||||
static fromInt(value) {
|
||||
var intValue = value | 0;
|
||||
asserts.assert(value === intValue, 'value should be a 32-bit integer');
|
||||
|
||||
if (-128 <= intValue && intValue < 128) {
|
||||
return getCachedIntValue_(intValue);
|
||||
} else {
|
||||
return new Long(intValue, intValue < 0 ? -1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Long representing the given value.
|
||||
* NaN will be returned as zero. Infinity is converted to max value and
|
||||
* -Infinity to min value.
|
||||
* @param {number} value The number in question.
|
||||
* @return {!Long} The corresponding Long value.
|
||||
*/
|
||||
static fromNumber(value) {
|
||||
if (value > 0) {
|
||||
if (value >= TWO_PWR_63_DBL_) {
|
||||
return Long.getMaxValue();
|
||||
}
|
||||
return new Long(value, value / TWO_PWR_32_DBL_);
|
||||
} else if (value < 0) {
|
||||
if (value <= -TWO_PWR_63_DBL_) {
|
||||
return Long.getMinValue();
|
||||
}
|
||||
return new Long(-value, -value / TWO_PWR_32_DBL_).negate();
|
||||
} else {
|
||||
// NaN or 0.
|
||||
return Long.getZero();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Long representing the 64-bit integer that comes by concatenating
|
||||
* the given high and low bits. Each is assumed to use 32 bits.
|
||||
* @param {number} lowBits The low 32-bits.
|
||||
* @param {number} highBits The high 32-bits.
|
||||
* @return {!Long} The corresponding Long value.
|
||||
*/
|
||||
static fromBits(lowBits, highBits) {
|
||||
return new Long(lowBits, highBits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Long representation of the given string, written using the given
|
||||
* radix.
|
||||
* @param {string} str The textual representation of the Long.
|
||||
* @param {number=} opt_radix The radix in which the text is written.
|
||||
* @return {!Long} The corresponding Long value.
|
||||
*/
|
||||
static fromString(str, opt_radix) {
|
||||
if (str.charAt(0) == '-') {
|
||||
return Long.fromString(str.substring(1), opt_radix).negate();
|
||||
}
|
||||
|
||||
// We can avoid very expensive multiply based code path for some common
|
||||
// cases.
|
||||
var numberValue = parseInt(str, opt_radix || 10);
|
||||
if (numberValue <= MAX_SAFE_INTEGER_) {
|
||||
return new Long(
|
||||
(numberValue % TWO_PWR_32_DBL_) | 0,
|
||||
(numberValue / TWO_PWR_32_DBL_) | 0);
|
||||
}
|
||||
|
||||
if (str.length == 0) {
|
||||
throw new Error('number format error: empty string');
|
||||
}
|
||||
if (str.indexOf('-') >= 0) {
|
||||
throw new Error('number format error: interior "-" character: ' + str);
|
||||
}
|
||||
|
||||
var radix = opt_radix || 10;
|
||||
if (radix < 2 || 36 < radix) {
|
||||
throw new Error('radix out of range: ' + radix);
|
||||
}
|
||||
|
||||
// Do several (8) digits each time through the loop, so as to
|
||||
// minimize the calls to the very expensive emulated multiply.
|
||||
var radixToPower = Long.fromNumber(Math.pow(radix, 8));
|
||||
|
||||
var result = Long.getZero();
|
||||
for (var i = 0; i < str.length; i += 8) {
|
||||
var size = Math.min(8, str.length - i);
|
||||
var value = parseInt(str.substring(i, i + size), radix);
|
||||
if (size < 8) {
|
||||
var power = Long.fromNumber(Math.pow(radix, size));
|
||||
result = result.multiply(power).add(Long.fromNumber(value));
|
||||
} else {
|
||||
result = result.multiply(radixToPower);
|
||||
result = result.add(Long.fromNumber(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean value of whether the input string is within a Long's
|
||||
* range. Assumes an input string containing only numeric characters with an
|
||||
* optional preceding '-'.
|
||||
* @param {string} str The textual representation of the Long.
|
||||
* @param {number=} opt_radix The radix in which the text is written.
|
||||
* @return {boolean} Whether the string is within the range of a Long.
|
||||
*/
|
||||
static isStringInRange(str, opt_radix) {
|
||||
var radix = opt_radix || 10;
|
||||
if (radix < 2 || 36 < radix) {
|
||||
throw new Error('radix out of range: ' + radix);
|
||||
}
|
||||
|
||||
var extremeValue = (str.charAt(0) == '-') ? MIN_VALUE_FOR_RADIX_[radix] :
|
||||
MAX_VALUE_FOR_RADIX_[radix];
|
||||
|
||||
if (str.length < extremeValue.length) {
|
||||
return true;
|
||||
} else if (str.length == extremeValue.length && str <= extremeValue) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getZero() {
|
||||
return ZERO_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getOne() {
|
||||
return ONE_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getNegOne() {
|
||||
return NEG_ONE_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getMaxValue() {
|
||||
return MAX_VALUE_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getMinValue() {
|
||||
return MIN_VALUE_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Long}
|
||||
* @public
|
||||
*/
|
||||
static getTwoPwr24() {
|
||||
return TWO_PWR_24_;
|
||||
}
|
||||
}
|
||||
|
||||
exports = Long;
|
||||
|
||||
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
|
||||
// from* methods on which they depend.
|
||||
|
||||
|
||||
/**
|
||||
* A cache of the Long representations of small integer values.
|
||||
* @type {!Object<number, !Long>}
|
||||
* @private @const
|
||||
*/
|
||||
const IntCache_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a cached long number representing the given (32-bit) integer value.
|
||||
* @param {number} value The 32-bit integer in question.
|
||||
* @return {!Long} The corresponding Long value.
|
||||
* @private
|
||||
*/
|
||||
function getCachedIntValue_(value) {
|
||||
return reflect.cache(IntCache_, value, function(val) {
|
||||
return new Long(val, val < 0 ? -1 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The array of maximum values of a Long in string representation for a given
|
||||
* radix between 2 and 36, inclusive.
|
||||
* @private @const {!Array<string>}
|
||||
*/
|
||||
const MAX_VALUE_FOR_RADIX_ = [
|
||||
'', '', // unused
|
||||
'111111111111111111111111111111111111111111111111111111111111111',
|
||||
// base 2
|
||||
'2021110011022210012102010021220101220221', // base 3
|
||||
'13333333333333333333333333333333', // base 4
|
||||
'1104332401304422434310311212', // base 5
|
||||
'1540241003031030222122211', // base 6
|
||||
'22341010611245052052300', // base 7
|
||||
'777777777777777777777', // base 8
|
||||
'67404283172107811827', // base 9
|
||||
'9223372036854775807', // base 10
|
||||
'1728002635214590697', // base 11
|
||||
'41a792678515120367', // base 12
|
||||
'10b269549075433c37', // base 13
|
||||
'4340724c6c71dc7a7', // base 14
|
||||
'160e2ad3246366807', // base 15
|
||||
'7fffffffffffffff', // base 16
|
||||
'33d3d8307b214008', // base 17
|
||||
'16agh595df825fa7', // base 18
|
||||
'ba643dci0ffeehh', // base 19
|
||||
'5cbfjia3fh26ja7', // base 20
|
||||
'2heiciiie82dh97', // base 21
|
||||
'1adaibb21dckfa7', // base 22
|
||||
'i6k448cf4192c2', // base 23
|
||||
'acd772jnc9l0l7', // base 24
|
||||
'64ie1focnn5g77', // base 25
|
||||
'3igoecjbmca687', // base 26
|
||||
'27c48l5b37oaop', // base 27
|
||||
'1bk39f3ah3dmq7', // base 28
|
||||
'q1se8f0m04isb', // base 29
|
||||
'hajppbc1fc207', // base 30
|
||||
'bm03i95hia437', // base 31
|
||||
'7vvvvvvvvvvvv', // base 32
|
||||
'5hg4ck9jd4u37', // base 33
|
||||
'3tdtk1v8j6tpp', // base 34
|
||||
'2pijmikexrxp7', // base 35
|
||||
'1y2p0ij32e8e7' // base 36
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The array of minimum values of a Long in string representation for a given
|
||||
* radix between 2 and 36, inclusive.
|
||||
* @private @const {!Array<string>}
|
||||
*/
|
||||
const MIN_VALUE_FOR_RADIX_ = [
|
||||
'', '', // unused
|
||||
'-1000000000000000000000000000000000000000000000000000000000000000',
|
||||
// base 2
|
||||
'-2021110011022210012102010021220101220222', // base 3
|
||||
'-20000000000000000000000000000000', // base 4
|
||||
'-1104332401304422434310311213', // base 5
|
||||
'-1540241003031030222122212', // base 6
|
||||
'-22341010611245052052301', // base 7
|
||||
'-1000000000000000000000', // base 8
|
||||
'-67404283172107811828', // base 9
|
||||
'-9223372036854775808', // base 10
|
||||
'-1728002635214590698', // base 11
|
||||
'-41a792678515120368', // base 12
|
||||
'-10b269549075433c38', // base 13
|
||||
'-4340724c6c71dc7a8', // base 14
|
||||
'-160e2ad3246366808', // base 15
|
||||
'-8000000000000000', // base 16
|
||||
'-33d3d8307b214009', // base 17
|
||||
'-16agh595df825fa8', // base 18
|
||||
'-ba643dci0ffeehi', // base 19
|
||||
'-5cbfjia3fh26ja8', // base 20
|
||||
'-2heiciiie82dh98', // base 21
|
||||
'-1adaibb21dckfa8', // base 22
|
||||
'-i6k448cf4192c3', // base 23
|
||||
'-acd772jnc9l0l8', // base 24
|
||||
'-64ie1focnn5g78', // base 25
|
||||
'-3igoecjbmca688', // base 26
|
||||
'-27c48l5b37oaoq', // base 27
|
||||
'-1bk39f3ah3dmq8', // base 28
|
||||
'-q1se8f0m04isc', // base 29
|
||||
'-hajppbc1fc208', // base 30
|
||||
'-bm03i95hia438', // base 31
|
||||
'-8000000000000', // base 32
|
||||
'-5hg4ck9jd4u38', // base 33
|
||||
'-3tdtk1v8j6tpq', // base 34
|
||||
'-2pijmikexrxp8', // base 35
|
||||
'-1y2p0ij32e8e8' // base 36
|
||||
];
|
||||
|
||||
/**
|
||||
* TODO(goktug): Replace with Number.MAX_SAFE_INTEGER when polyfil is guaranteed
|
||||
* to be removed.
|
||||
* @type {number}
|
||||
* @private @const
|
||||
*/
|
||||
const MAX_SAFE_INTEGER_ = 0x1fffffffffffff;
|
||||
|
||||
// NOTE: the compiler should inline these constant values below and then remove
|
||||
// these variables, so there should be no runtime penalty for these.
|
||||
|
||||
/**
|
||||
* Number used repeated below in calculations. This must appear before the
|
||||
* first call to any from* function above.
|
||||
* @const {number}
|
||||
* @private
|
||||
*/
|
||||
const TWO_PWR_32_DBL_ = 0x100000000;
|
||||
|
||||
|
||||
/**
|
||||
* @const {number}
|
||||
* @private
|
||||
*/
|
||||
const TWO_PWR_63_DBL_ = 0x8000000000000000;
|
||||
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const ZERO_ = Long.fromBits(0, 0);
|
||||
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const ONE_ = Long.fromBits(1, 0);
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const NEG_ONE_ = Long.fromBits(-1, -1);
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const MAX_VALUE_ = Long.fromBits(0xFFFFFFFF, 0x7FFFFFFF);
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const MIN_VALUE_ = Long.fromBits(0, 0x80000000);
|
||||
|
||||
/**
|
||||
* @private @const {!Long}
|
||||
*/
|
||||
const TWO_PWR_24_ = Long.fromBits(1 << 24, 0);
|
||||
Executable
+477
@@ -0,0 +1,477 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Additional mathematical functions.
|
||||
*/
|
||||
|
||||
goog.provide('goog.math');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Returns a random integer greater than or equal to 0 and less than `a`.
|
||||
* @param {number} a The upper bound for the random integer (exclusive).
|
||||
* @return {number} A random integer N such that 0 <= N < a.
|
||||
*/
|
||||
goog.math.randomInt = function(a) {
|
||||
'use strict';
|
||||
return Math.floor(Math.random() * a);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a random number greater than or equal to `a` and less than
|
||||
* `b`.
|
||||
* @param {number} a The lower bound for the random number (inclusive).
|
||||
* @param {number} b The upper bound for the random number (exclusive).
|
||||
* @return {number} A random number N such that a <= N < b.
|
||||
*/
|
||||
goog.math.uniformRandom = function(a, b) {
|
||||
'use strict';
|
||||
return a + Math.random() * (b - a);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes a number and clamps it to within the provided bounds.
|
||||
* @param {number} value The input number.
|
||||
* @param {number} min The minimum value to return.
|
||||
* @param {number} max The maximum value to return.
|
||||
* @return {number} The input number if it is within bounds, or the nearest
|
||||
* number within the bounds.
|
||||
*/
|
||||
goog.math.clamp = function(value, min, max) {
|
||||
'use strict';
|
||||
return Math.min(Math.max(value, min), max);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The % operator in JavaScript returns the remainder of a / b, but differs from
|
||||
* some other languages in that the result will have the same sign as the
|
||||
* dividend. For example, -1 % 8 == -1, whereas in some other languages
|
||||
* (such as Python) the result would be 7. This function emulates the more
|
||||
* correct modulo behavior, which is useful for certain applications such as
|
||||
* calculating an offset index in a circular list.
|
||||
*
|
||||
* @param {number} a The dividend.
|
||||
* @param {number} b The divisor.
|
||||
* @return {number} a % b where the result is between 0 and b (either 0 <= x < b
|
||||
* or b < x <= 0, depending on the sign of b).
|
||||
*/
|
||||
goog.math.modulo = function(a, b) {
|
||||
'use strict';
|
||||
var r = a % b;
|
||||
// If r and b differ in sign, add b to wrap the result to the correct sign.
|
||||
return (r * b < 0) ? r + b : r;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs linear interpolation between values a and b. Returns the value
|
||||
* between a and b proportional to x (when x is between 0 and 1. When x is
|
||||
* outside this range, the return value is a linear extrapolation).
|
||||
* @param {number} a A number.
|
||||
* @param {number} b A number.
|
||||
* @param {number} x The proportion between a and b.
|
||||
* @return {number} The interpolated value between a and b.
|
||||
*/
|
||||
goog.math.lerp = function(a, b, x) {
|
||||
'use strict';
|
||||
return a + x * (b - a);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the two values are equal to each other, within a certain
|
||||
* tolerance to adjust for floating point errors.
|
||||
* @param {number} a A number.
|
||||
* @param {number} b A number.
|
||||
* @param {number=} opt_tolerance Optional tolerance range. Defaults
|
||||
* to 0.000001. If specified, should be greater than 0.
|
||||
* @return {boolean} Whether `a` and `b` are nearly equal.
|
||||
*/
|
||||
goog.math.nearlyEquals = function(a, b, opt_tolerance) {
|
||||
'use strict';
|
||||
return Math.abs(a - b) <= (opt_tolerance || 0.000001);
|
||||
};
|
||||
|
||||
|
||||
// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
|
||||
// alias.
|
||||
/**
|
||||
* Normalizes an angle to be in range [0-360). Angles outside this range will
|
||||
* be normalized to be the equivalent angle with that range.
|
||||
* @param {number} angle Angle in degrees.
|
||||
* @return {number} Standardized angle.
|
||||
*/
|
||||
goog.math.standardAngle = function(angle) {
|
||||
'use strict';
|
||||
return goog.math.modulo(angle, 360);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes an angle to be in range [0-2*PI). Angles outside this range will
|
||||
* be normalized to be the equivalent angle with that range.
|
||||
* @param {number} angle Angle in radians.
|
||||
* @return {number} Standardized angle.
|
||||
*/
|
||||
goog.math.standardAngleInRadians = function(angle) {
|
||||
'use strict';
|
||||
return goog.math.modulo(angle, 2 * Math.PI);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts degrees to radians.
|
||||
* @param {number} angleDegrees Angle in degrees.
|
||||
* @return {number} Angle in radians.
|
||||
*/
|
||||
goog.math.toRadians = function(angleDegrees) {
|
||||
'use strict';
|
||||
return angleDegrees * Math.PI / 180;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts radians to degrees.
|
||||
* @param {number} angleRadians Angle in radians.
|
||||
* @return {number} Angle in degrees.
|
||||
*/
|
||||
goog.math.toDegrees = function(angleRadians) {
|
||||
'use strict';
|
||||
return angleRadians * 180 / Math.PI;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* For a given angle and radius, finds the X portion of the offset.
|
||||
* @param {number} degrees Angle in degrees (zero points in +X direction).
|
||||
* @param {number} radius Radius.
|
||||
* @return {number} The x-distance for the angle and radius.
|
||||
*/
|
||||
goog.math.angleDx = function(degrees, radius) {
|
||||
'use strict';
|
||||
return radius * Math.cos(goog.math.toRadians(degrees));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* For a given angle and radius, finds the Y portion of the offset.
|
||||
* @param {number} degrees Angle in degrees (zero points in +X direction).
|
||||
* @param {number} radius Radius.
|
||||
* @return {number} The y-distance for the angle and radius.
|
||||
*/
|
||||
goog.math.angleDy = function(degrees, radius) {
|
||||
'use strict';
|
||||
return radius * Math.sin(goog.math.toRadians(degrees));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Computes the angle between two points (x1,y1) and (x2,y2).
|
||||
* Angle zero points in the +X direction, 90 degrees points in the +Y
|
||||
* direction (down) and from there we grow clockwise towards 360 degrees.
|
||||
* @param {number} x1 x of first point.
|
||||
* @param {number} y1 y of first point.
|
||||
* @param {number} x2 x of second point.
|
||||
* @param {number} y2 y of second point.
|
||||
* @return {number} Standardized angle in degrees of the vector from
|
||||
* x1,y1 to x2,y2.
|
||||
*/
|
||||
goog.math.angle = function(x1, y1, x2, y2) {
|
||||
'use strict';
|
||||
return goog.math.standardAngle(
|
||||
goog.math.toDegrees(Math.atan2(y2 - y1, x2 - x1)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Computes the difference between startAngle and endAngle (angles in degrees).
|
||||
* @param {number} startAngle Start angle in degrees.
|
||||
* @param {number} endAngle End angle in degrees.
|
||||
* @return {number} The number of degrees that when added to
|
||||
* startAngle will result in endAngle. Positive numbers mean that the
|
||||
* direction is clockwise. Negative numbers indicate a counter-clockwise
|
||||
* direction.
|
||||
* The shortest route (clockwise vs counter-clockwise) between the angles
|
||||
* is used.
|
||||
* When the difference is 180 degrees, the function returns 180 (not -180)
|
||||
* angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
|
||||
* angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
|
||||
*/
|
||||
goog.math.angleDifference = function(startAngle, endAngle) {
|
||||
'use strict';
|
||||
var d =
|
||||
goog.math.standardAngle(endAngle) - goog.math.standardAngle(startAngle);
|
||||
if (d > 180) {
|
||||
d = d - 360;
|
||||
} else if (d <= -180) {
|
||||
d = 360 + d;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sign of a number as per the "sign" or "signum" function.
|
||||
* @param {number} x The number to take the sign of.
|
||||
* @return {number} -1 when negative, 1 when positive, 0 when 0. Preserves
|
||||
* signed zeros and NaN.
|
||||
*/
|
||||
goog.math.sign = function(x) {
|
||||
'use strict';
|
||||
if (x > 0) {
|
||||
return 1;
|
||||
}
|
||||
if (x < 0) {
|
||||
return -1;
|
||||
}
|
||||
return x; // Preserves signed zeros and NaN.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* JavaScript implementation of Longest Common Subsequence problem.
|
||||
* http://en.wikipedia.org/wiki/Longest_common_subsequence
|
||||
*
|
||||
* Returns the longest possible array that is subarray of both of given arrays.
|
||||
*
|
||||
* @param {IArrayLike<S>} array1 First array of objects.
|
||||
* @param {IArrayLike<T>} array2 Second array of objects.
|
||||
* @param {Function=} opt_compareFn Function that acts as a custom comparator
|
||||
* for the array ojects. Function should return true if objects are equal,
|
||||
* otherwise false.
|
||||
* @param {Function=} opt_collectorFn Function used to decide what to return
|
||||
* as a result subsequence. It accepts 2 arguments: index of common element
|
||||
* in the first array and index in the second. The default function returns
|
||||
* element from the first array.
|
||||
* @return {!Array<S|T>} A list of objects that are common to both arrays
|
||||
* such that there is no common subsequence with size greater than the
|
||||
* length of the list.
|
||||
* @template S,T
|
||||
*/
|
||||
goog.math.longestCommonSubsequence = function(
|
||||
array1, array2, opt_compareFn, opt_collectorFn) {
|
||||
'use strict';
|
||||
var compare = opt_compareFn || function(a, b) {
|
||||
'use strict';
|
||||
return a == b;
|
||||
};
|
||||
|
||||
var collect = opt_collectorFn || function(i1, i2) {
|
||||
'use strict';
|
||||
return array1[i1];
|
||||
};
|
||||
|
||||
var length1 = array1.length;
|
||||
var length2 = array2.length;
|
||||
|
||||
var arr = [];
|
||||
for (var i = 0; i < length1 + 1; i++) {
|
||||
arr[i] = [];
|
||||
arr[i][0] = 0;
|
||||
}
|
||||
|
||||
for (var j = 0; j < length2 + 1; j++) {
|
||||
arr[0][j] = 0;
|
||||
}
|
||||
|
||||
for (i = 1; i <= length1; i++) {
|
||||
for (j = 1; j <= length2; j++) {
|
||||
if (compare(array1[i - 1], array2[j - 1])) {
|
||||
arr[i][j] = arr[i - 1][j - 1] + 1;
|
||||
} else {
|
||||
arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backtracking
|
||||
var result = [];
|
||||
var i = length1, j = length2;
|
||||
while (i > 0 && j > 0) {
|
||||
if (compare(array1[i - 1], array2[j - 1])) {
|
||||
result.unshift(collect(i - 1, j - 1));
|
||||
i--;
|
||||
j--;
|
||||
} else {
|
||||
if (arr[i - 1][j] > arr[i][j - 1]) {
|
||||
i--;
|
||||
} else {
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sum of the arguments.
|
||||
* @param {...number} var_args Numbers to add.
|
||||
* @return {number} The sum of the arguments (0 if no arguments were provided,
|
||||
* `NaN` if any of the arguments is not a valid number).
|
||||
*/
|
||||
goog.math.sum = function(var_args) {
|
||||
'use strict';
|
||||
return /** @type {number} */ (
|
||||
Array.prototype.reduce.call(arguments, function(sum, value) {
|
||||
'use strict';
|
||||
return sum + value;
|
||||
}, 0));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the arithmetic mean of the arguments.
|
||||
* @param {...number} var_args Numbers to average.
|
||||
* @return {number} The average of the arguments (`NaN` if no arguments
|
||||
* were provided or any of the arguments is not a valid number).
|
||||
*/
|
||||
goog.math.average = function(var_args) {
|
||||
'use strict';
|
||||
return goog.math.sum.apply(null, arguments) / arguments.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the unbiased sample variance of the arguments. For a definition,
|
||||
* see e.g. http://en.wikipedia.org/wiki/Variance
|
||||
* @param {...number} var_args Number samples to analyze.
|
||||
* @return {number} The unbiased sample variance of the arguments (0 if fewer
|
||||
* than two samples were provided, or `NaN` if any of the samples is
|
||||
* not a valid number).
|
||||
*/
|
||||
goog.math.sampleVariance = function(var_args) {
|
||||
'use strict';
|
||||
var sampleSize = arguments.length;
|
||||
if (sampleSize < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var mean = goog.math.average.apply(null, arguments);
|
||||
var variance = goog.math.sum.apply(
|
||||
null,
|
||||
Array.prototype.map.call(
|
||||
arguments,
|
||||
function(val) {
|
||||
'use strict';
|
||||
return Math.pow(val - mean, 2);
|
||||
})) /
|
||||
(sampleSize - 1);
|
||||
|
||||
return variance;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sample standard deviation of the arguments. For a definition of
|
||||
* sample standard deviation, see e.g.
|
||||
* http://en.wikipedia.org/wiki/Standard_deviation
|
||||
* @param {...number} var_args Number samples to analyze.
|
||||
* @return {number} The sample standard deviation of the arguments (0 if fewer
|
||||
* than two samples were provided, or `NaN` if any of the samples is
|
||||
* not a valid number).
|
||||
*/
|
||||
goog.math.standardDeviation = function(var_args) {
|
||||
'use strict';
|
||||
return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the supplied number represents an integer, i.e. that is has
|
||||
* no fractional component. No range-checking is performed on the number.
|
||||
* @param {number} num The number to test.
|
||||
* @return {boolean} Whether `num` is an integer.
|
||||
*/
|
||||
goog.math.isInt = function(num) {
|
||||
'use strict';
|
||||
return isFinite(num) && num % 1 == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the supplied number is finite and not NaN.
|
||||
* @param {number} num The number to test.
|
||||
* @return {boolean} Whether `num` is a finite number.
|
||||
* @deprecated Use {@link isFinite} instead.
|
||||
*/
|
||||
goog.math.isFiniteNumber = function(num) {
|
||||
'use strict';
|
||||
return isFinite(num);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} num The number to test.
|
||||
* @return {boolean} Whether it is negative zero.
|
||||
*/
|
||||
goog.math.isNegativeZero = function(num) {
|
||||
'use strict';
|
||||
return num == 0 && 1 / num < 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the precise value of floor(log10(num)).
|
||||
* Simpler implementations didn't work because of floating point rounding
|
||||
* errors. For example
|
||||
* <ul>
|
||||
* <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
|
||||
* <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
|
||||
* <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
|
||||
* </ul>
|
||||
* @param {number} num A floating point number.
|
||||
* @return {number} Its logarithm to base 10 rounded down to the nearest
|
||||
* integer if num > 0. -Infinity if num == 0. NaN if num < 0.
|
||||
*/
|
||||
goog.math.log10Floor = function(num) {
|
||||
'use strict';
|
||||
if (num > 0) {
|
||||
var x = Math.round(Math.log(num) * Math.LOG10E);
|
||||
return x - (parseFloat('1e' + x) > num ? 1 : 0);
|
||||
}
|
||||
return num == 0 ? -Infinity : NaN;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A tweaked variant of `Math.floor` which tolerates if the passed number
|
||||
* is infinitesimally smaller than the closest integer. It often happens with
|
||||
* the results of floating point calculations because of the finite precision
|
||||
* of the intermediate results. For example {@code Math.floor(Math.log(1000) /
|
||||
* Math.LN10) == 2}, not 3 as one would expect.
|
||||
* @param {number} num A number.
|
||||
* @param {number=} opt_epsilon An infinitesimally small positive number, the
|
||||
* rounding error to tolerate.
|
||||
* @return {number} The largest integer less than or equal to `num`.
|
||||
*/
|
||||
goog.math.safeFloor = function(num, opt_epsilon) {
|
||||
'use strict';
|
||||
goog.asserts.assert(opt_epsilon === undefined || opt_epsilon > 0);
|
||||
return Math.floor(num + (opt_epsilon || 2e-15));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A tweaked variant of `Math.ceil`. See `goog.math.safeFloor` for
|
||||
* details.
|
||||
* @param {number} num A number.
|
||||
* @param {number=} opt_epsilon An infinitesimally small positive number, the
|
||||
* rounding error to tolerate.
|
||||
* @return {number} The smallest integer greater than or equal to `num`.
|
||||
*/
|
||||
goog.math.safeCeil = function(num, opt_epsilon) {
|
||||
'use strict';
|
||||
goog.asserts.assert(opt_epsilon === undefined || opt_epsilon > 0);
|
||||
return Math.ceil(num - (opt_epsilon || 2e-15));
|
||||
};
|
||||
Executable
+235
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A utility class for representing two-dimensional sizes.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.math.Size');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for representing sizes consisting of a width and height. Undefined
|
||||
* width and height support is deprecated and results in compiler warning.
|
||||
* @param {number} width Width.
|
||||
* @param {number} height Height.
|
||||
* @struct
|
||||
* @constructor
|
||||
*/
|
||||
goog.math.Size = function(width, height) {
|
||||
'use strict';
|
||||
/**
|
||||
* Width
|
||||
* @type {number}
|
||||
*/
|
||||
this.width = width;
|
||||
|
||||
/**
|
||||
* Height
|
||||
* @type {number}
|
||||
*/
|
||||
this.height = height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares sizes for equality.
|
||||
* @param {goog.math.Size} a A Size.
|
||||
* @param {goog.math.Size} b A Size.
|
||||
* @return {boolean} True iff the sizes have equal widths and equal
|
||||
* heights, or if both are null.
|
||||
*/
|
||||
goog.math.Size.equals = function(a, b) {
|
||||
'use strict';
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return a.width == b.width && a.height == b.height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.math.Size} A new copy of the Size.
|
||||
*/
|
||||
goog.math.Size.prototype.clone = function() {
|
||||
'use strict';
|
||||
return new goog.math.Size(this.width, this.height);
|
||||
};
|
||||
|
||||
|
||||
if (goog.DEBUG) {
|
||||
/**
|
||||
* Returns a nice string representing size.
|
||||
* @return {string} In the form (50 x 73).
|
||||
* @override
|
||||
*/
|
||||
goog.math.Size.prototype.toString = function() {
|
||||
'use strict';
|
||||
return '(' + this.width + ' x ' + this.height + ')';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The longer of the two dimensions in the size.
|
||||
*/
|
||||
goog.math.Size.prototype.getLongest = function() {
|
||||
'use strict';
|
||||
return Math.max(this.width, this.height);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The shorter of the two dimensions in the size.
|
||||
*/
|
||||
goog.math.Size.prototype.getShortest = function() {
|
||||
'use strict';
|
||||
return Math.min(this.width, this.height);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The area of the size (width * height).
|
||||
*/
|
||||
goog.math.Size.prototype.area = function() {
|
||||
'use strict';
|
||||
return this.width * this.height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The perimeter of the size (width + height) * 2.
|
||||
*/
|
||||
goog.math.Size.prototype.perimeter = function() {
|
||||
'use strict';
|
||||
return (this.width + this.height) * 2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The ratio of the size's width to its height.
|
||||
*/
|
||||
goog.math.Size.prototype.aspectRatio = function() {
|
||||
'use strict';
|
||||
return this.width / this.height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the size has zero area, false if both dimensions
|
||||
* are non-zero numbers.
|
||||
*/
|
||||
goog.math.Size.prototype.isEmpty = function() {
|
||||
'use strict';
|
||||
return !this.area();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clamps the width and height parameters upward to integer values.
|
||||
* @return {!goog.math.Size} This size with ceil'd components.
|
||||
*/
|
||||
goog.math.Size.prototype.ceil = function() {
|
||||
'use strict';
|
||||
this.width = Math.ceil(this.width);
|
||||
this.height = Math.ceil(this.height);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!goog.math.Size} target The target size.
|
||||
* @return {boolean} True if this Size is the same size or smaller than the
|
||||
* target size in both dimensions.
|
||||
*/
|
||||
goog.math.Size.prototype.fitsInside = function(target) {
|
||||
'use strict';
|
||||
return this.width <= target.width && this.height <= target.height;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clamps the width and height parameters downward to integer values.
|
||||
* @return {!goog.math.Size} This size with floored components.
|
||||
*/
|
||||
goog.math.Size.prototype.floor = function() {
|
||||
'use strict';
|
||||
this.width = Math.floor(this.width);
|
||||
this.height = Math.floor(this.height);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rounds the width and height parameters to integer values.
|
||||
* @return {!goog.math.Size} This size with rounded components.
|
||||
*/
|
||||
goog.math.Size.prototype.round = function() {
|
||||
'use strict';
|
||||
this.width = Math.round(this.width);
|
||||
this.height = Math.round(this.height);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scales this size by the given scale factors. The width and height are scaled
|
||||
* by `sx` and `opt_sy` respectively. If `opt_sy` is not
|
||||
* given, then `sx` is used for both the width and height.
|
||||
* @param {number} sx The scale factor to use for the width.
|
||||
* @param {number=} opt_sy The scale factor to use for the height.
|
||||
* @return {!goog.math.Size} This Size object after scaling.
|
||||
*/
|
||||
goog.math.Size.prototype.scale = function(sx, opt_sy) {
|
||||
'use strict';
|
||||
const sy = (typeof opt_sy === 'number') ? opt_sy : sx;
|
||||
this.width *= sx;
|
||||
this.height *= sy;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Uniformly scales the size to perfectly cover the dimensions of a given size.
|
||||
* If the size is already larger than the target, it will be scaled down to the
|
||||
* minimum size at which it still covers the entire target. The original aspect
|
||||
* ratio will be preserved.
|
||||
*
|
||||
* This function assumes that both Sizes contain strictly positive dimensions.
|
||||
* @param {!goog.math.Size} target The target size.
|
||||
* @return {!goog.math.Size} This Size object, after optional scaling.
|
||||
*/
|
||||
goog.math.Size.prototype.scaleToCover = function(target) {
|
||||
'use strict';
|
||||
const s = this.aspectRatio() <= target.aspectRatio() ?
|
||||
target.width / this.width :
|
||||
target.height / this.height;
|
||||
|
||||
return this.scale(s);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Uniformly scales the size to fit inside the dimensions of a given size. The
|
||||
* original aspect ratio will be preserved.
|
||||
*
|
||||
* This function assumes that both Sizes contain strictly positive dimensions.
|
||||
* @param {!goog.math.Size} target The target size.
|
||||
* @return {!goog.math.Size} This Size object, after optional scaling.
|
||||
*/
|
||||
goog.math.Size.prototype.scaleToFit = function(target) {
|
||||
'use strict';
|
||||
const s = this.aspectRatio() > target.aspectRatio() ?
|
||||
target.width / this.width :
|
||||
target.height / this.height;
|
||||
|
||||
return this.scale(s);
|
||||
};
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Tool for caching the result of expensive deterministic
|
||||
* functions.
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Memoization
|
||||
*/
|
||||
|
||||
goog.module('goog.memoize');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const reflect = goog.require('goog.reflect');
|
||||
|
||||
/**
|
||||
* Note that when using the WeakMap polyfill users may run into issues
|
||||
* where memoize is unable to store a cache properly (as the polyfill tries to
|
||||
* store the values on the key object as properties.). The workaround is to not
|
||||
* memoize onto a sealed context if the code needs to run in browsers where
|
||||
* WeakMap is not available (IE<=10 as an example).
|
||||
* @type {!WeakMap<!Object, !Object>}
|
||||
*/
|
||||
const MODULE_LOCAL_CACHE = new WeakMap();
|
||||
|
||||
/**
|
||||
* Decorator around functions that caches the inner function's return values.
|
||||
*
|
||||
* To cache parameterless functions, see goog.functions.cacheReturnValue.
|
||||
*
|
||||
* @param {Function} f The function to wrap. Its return value may only depend
|
||||
* on its arguments and 'this' context. There may be further restrictions
|
||||
* on the arguments depending on the capabilities of the serializer used.
|
||||
* @param {function(number, !IArrayLike<?>): string=} serializer A function to
|
||||
* serialize f's arguments. It must have the same signature as
|
||||
* goog.memoize.simpleSerializer. It defaults to that function.
|
||||
* @return {!Function} The wrapped function.
|
||||
*/
|
||||
function memoize(f, serializer = simpleSerializer) {
|
||||
const uidF = goog.getUid(f);
|
||||
const keyFn = ([that, ...args]) => serializer(uidF, args);
|
||||
const valueFn = ([that, ...args]) => f.apply(that, args);
|
||||
|
||||
/**
|
||||
* @this {Object} The object whose function is being wrapped.
|
||||
* @param {...*} args
|
||||
* @return {?} the return value of the original function.
|
||||
*/
|
||||
const memoizedFn = function(...args) {
|
||||
if (memoize.ENABLE_MEMOIZE) {
|
||||
const cacheKey = this || goog.global;
|
||||
let cache = MODULE_LOCAL_CACHE.get(cacheKey);
|
||||
if (!cache) {
|
||||
cache = {};
|
||||
MODULE_LOCAL_CACHE.set(cacheKey, cache);
|
||||
}
|
||||
return reflect.cache(cache, [this, ...args], valueFn, keyFn);
|
||||
} else {
|
||||
return f.apply(this, args);
|
||||
}
|
||||
};
|
||||
return memoizedFn;
|
||||
}
|
||||
exports = memoize;
|
||||
|
||||
/**
|
||||
* @define {boolean} Flag to disable memoization in unit tests.
|
||||
*/
|
||||
memoize.ENABLE_MEMOIZE = goog.define('goog.memoize.ENABLE_MEMOIZE', true);
|
||||
|
||||
|
||||
/**
|
||||
* Clears the memoization cache on the given object.
|
||||
* @param {?Object} cacheOwner The owner of the cache.
|
||||
*/
|
||||
const clearCache = function(cacheOwner) {
|
||||
MODULE_LOCAL_CACHE.set(cacheOwner || goog.global, {});
|
||||
};
|
||||
exports.clearCache = clearCache;
|
||||
|
||||
|
||||
/**
|
||||
* Simple and fast argument serializer function for goog.memoize.
|
||||
* Supports string, number, boolean, null and undefined arguments. Doesn't
|
||||
* support \x0B characters in the strings.
|
||||
* @param {number} functionUid Unique identifier of the function whose result
|
||||
* is cached.
|
||||
* @param {!IArrayLike<?>} args The arguments that the function to memoize is
|
||||
* called with. Note: it is an array-like object, because it supports
|
||||
* indexing and has the length property.
|
||||
* @return {string} The list of arguments with type information concatenated
|
||||
* with the functionUid argument, serialized as \x0B-separated string.
|
||||
*/
|
||||
const simpleSerializer = function(functionUid, args) {
|
||||
const context = [functionUid];
|
||||
for (let i = args.length - 1; i >= 0; --i) {
|
||||
context.push(typeof args[i], args[i]);
|
||||
}
|
||||
return context.join('\x0B');
|
||||
};
|
||||
exports.simpleSerializer = simpleSerializer;
|
||||
Executable
+506
@@ -0,0 +1,506 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Functions for setting, getting and deleting cookies.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.Cookies');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for handling browser cookies.
|
||||
* @param {?Document} context The context document to get/set cookies on.
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.net.Cookies = function(context) {
|
||||
'use strict';
|
||||
/**
|
||||
* The context document to get/set cookies on. If no document context is
|
||||
* passed, use a fake one with only the "cookie" attribute. This allows
|
||||
* this class to be instantiated safely in web worker environments.
|
||||
* @private {{cookie: string}}
|
||||
*/
|
||||
this.document_ = context || {cookie: ''};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static constant for the size of cookies. Per the spec, there's a 4K limit
|
||||
* to the size of a cookie. To make sure users can't break this limit, we
|
||||
* should truncate long cookies at 3950 bytes, to be extra careful with dumb
|
||||
* browsers/proxies that interpret 4K as 4000 rather than 4096.
|
||||
* @const {number}
|
||||
*/
|
||||
goog.net.Cookies.MAX_COOKIE_LENGTH = 3950;
|
||||
|
||||
|
||||
/**
|
||||
* The name of the test cookie to set.
|
||||
*
|
||||
*
|
||||
* @private @const {string}
|
||||
*/
|
||||
goog.net.Cookies.TEST_COOKIE_NAME_ = 'TESTCOOKIESENABLED';
|
||||
|
||||
|
||||
/**
|
||||
* The value of the test cookie to set.
|
||||
* @private @const {string}
|
||||
*/
|
||||
goog.net.Cookies.TEST_COOKIE_VALUE_ = '1';
|
||||
|
||||
|
||||
/**
|
||||
* Max age of the test cookie in seconds.
|
||||
* @private @const {number}
|
||||
*/
|
||||
goog.net.Cookies.TEST_COOKIE_MAX_AGE_ = 60;
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if cookies are enabled.
|
||||
*
|
||||
* navigator.cookieEnabled is an unreliable API in some browsers such as
|
||||
* Internet Explorer. It will return true even when cookies are actually
|
||||
* blocked. To work around this, check for the presence of cookies, or attempt
|
||||
* to manually set and retrieve a cookie, which is the ultimate test of whether
|
||||
* or not a browser supports cookies.
|
||||
*
|
||||
* @return {boolean} True if cookies are enabled.
|
||||
*/
|
||||
goog.net.Cookies.prototype.isEnabled = function() {
|
||||
'use strict';
|
||||
if (!goog.global.navigator.cookieEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.isEmpty()) {
|
||||
// There are some cookies already set for the current domain, so cookies
|
||||
// can't be totally blocked.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try setting and reading back a cookie to see if cookies are enabled.
|
||||
this.set(
|
||||
goog.net.Cookies.TEST_COOKIE_NAME_, goog.net.Cookies.TEST_COOKIE_VALUE_,
|
||||
{maxAge: goog.net.Cookies.TEST_COOKIE_MAX_AGE_});
|
||||
if (this.get(goog.net.Cookies.TEST_COOKIE_NAME_) !==
|
||||
goog.net.Cookies.TEST_COOKIE_VALUE_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clean up the test cookie.
|
||||
this.remove(goog.net.Cookies.TEST_COOKIE_NAME_);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* We do not allow '=', ';', or white space in the name.
|
||||
*
|
||||
* NOTE: The following are allowed by this method, but should be avoided for
|
||||
* cookies handled by the server.
|
||||
* - any name starting with '$'
|
||||
* - 'Comment'
|
||||
* - 'Domain'
|
||||
* - 'Expires'
|
||||
* - 'Max-Age'
|
||||
* - 'Path'
|
||||
* - 'Secure'
|
||||
* - 'Version'
|
||||
*
|
||||
* @param {string} name Cookie name.
|
||||
* @return {boolean} Whether name is valid.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
|
||||
*/
|
||||
goog.net.Cookies.prototype.isValidName = function(name) {
|
||||
'use strict';
|
||||
return !(/[;=\s]/.test(name));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* We do not allow ';' or line break in the value.
|
||||
*
|
||||
* Spec does not mention any illegal characters, but in practice semi-colons
|
||||
* break parsing and line breaks truncate the name.
|
||||
*
|
||||
* @param {string} value Cookie value.
|
||||
* @return {boolean} Whether value is valid.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2965">RFC 2965</a>
|
||||
*/
|
||||
goog.net.Cookies.prototype.isValidValue = function(value) {
|
||||
'use strict';
|
||||
return !(/[;\r\n]/.test(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a cookie. The max_age can be -1 to set a session cookie. To remove and
|
||||
* expire cookies, use remove() instead.
|
||||
*
|
||||
* Neither the `name` nor the `value` are encoded in any way. It is
|
||||
* up to the callers of `get` and `set` (as well as all the other
|
||||
* methods) to handle any possible encoding and decoding.
|
||||
*
|
||||
* @throws {!Error} If the `name` fails #goog.net.cookies.isValidName.
|
||||
* @throws {!Error} If the `value` fails #goog.net.cookies.isValidValue.
|
||||
*
|
||||
* @param {string} name The cookie name.
|
||||
* @param {string} value The cookie value.
|
||||
* @param {!goog.net.Cookies.SetOptions=} options The options object.
|
||||
*/
|
||||
goog.net.Cookies.prototype.set = function(name, value, options) {
|
||||
'use strict';
|
||||
/** @type {number|undefined} */
|
||||
let maxAge;
|
||||
/** @type {string|undefined} */
|
||||
let path;
|
||||
/** @type {string|undefined} */
|
||||
let domain;
|
||||
/** @type {boolean} */
|
||||
let secure = false;
|
||||
/** @type {!goog.net.Cookies.SameSite|undefined} */
|
||||
let sameSite;
|
||||
|
||||
if (typeof options === 'object') {
|
||||
sameSite = options.sameSite;
|
||||
secure = options.secure || false;
|
||||
domain = options.domain || undefined;
|
||||
path = options.path || undefined;
|
||||
maxAge = options.maxAge;
|
||||
}
|
||||
if (!this.isValidName(name)) {
|
||||
throw new Error('Invalid cookie name "' + name + '"');
|
||||
}
|
||||
if (!this.isValidValue(value)) {
|
||||
throw new Error('Invalid cookie value "' + value + '"');
|
||||
}
|
||||
|
||||
if (maxAge === undefined) {
|
||||
maxAge = -1;
|
||||
}
|
||||
|
||||
const domainStr = domain ? ';domain=' + domain : '';
|
||||
const pathStr = path ? ';path=' + path : '';
|
||||
const secureStr = secure ? ';secure' : '';
|
||||
|
||||
let expiresStr;
|
||||
|
||||
// Case 1: Set a session cookie.
|
||||
if (maxAge < 0) {
|
||||
expiresStr = '';
|
||||
|
||||
// Case 2: Remove the cookie.
|
||||
// Note: We don't tell people about this option in the function doc because
|
||||
// we prefer people to use remove() to remove cookies.
|
||||
} else if (maxAge == 0) {
|
||||
// Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert
|
||||
// it to local time, and if the local time is before Jan 1, 1970, then the
|
||||
// browser will ignore the Expires attribute altogether.
|
||||
const pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970
|
||||
expiresStr = ';expires=' + pastDate.toUTCString();
|
||||
|
||||
// Case 3: Set a persistent cookie.
|
||||
} else {
|
||||
const futureDate = new Date(Date.now() + maxAge * 1000);
|
||||
expiresStr = ';expires=' + futureDate.toUTCString();
|
||||
}
|
||||
|
||||
const sameSiteStr = sameSite != null ? ';samesite=' + sameSite : '';
|
||||
|
||||
this.setCookie_(
|
||||
name + '=' + value + domainStr + pathStr + expiresStr + secureStr +
|
||||
sameSiteStr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value for the first cookie with the given name.
|
||||
* @param {string} name The name of the cookie to get.
|
||||
* @param {string=} opt_default If not found this is returned instead.
|
||||
* @return {string|undefined} The value of the cookie. If no cookie is set this
|
||||
* returns opt_default or undefined if opt_default is not provided.
|
||||
*/
|
||||
goog.net.Cookies.prototype.get = function(name, opt_default) {
|
||||
'use strict';
|
||||
const nameEq = name + '=';
|
||||
const parts = this.getParts_();
|
||||
for (let i = 0, part; i < parts.length; i++) {
|
||||
part = goog.string.trim(parts[i]);
|
||||
// startsWith
|
||||
if (part.lastIndexOf(nameEq, 0) == 0) {
|
||||
return part.substr(nameEq.length);
|
||||
}
|
||||
if (part == name) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return opt_default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes and expires a cookie.
|
||||
* @param {string} name The cookie name.
|
||||
* @param {?string=} opt_path The path of the cookie. If null or not present,
|
||||
* expires the cookie set at the full request path.
|
||||
* @param {?string=} opt_domain The domain of the cookie, or null to expire a
|
||||
* cookie set at the full request host name. If not provided, the default is
|
||||
* null (i.e. cookie at full request host name).
|
||||
* @return {boolean} Whether the cookie existed before it was removed.
|
||||
*/
|
||||
goog.net.Cookies.prototype.remove = function(name, opt_path, opt_domain) {
|
||||
'use strict';
|
||||
const rv = this.containsKey(name);
|
||||
this.set(name, '', {maxAge: 0, path: opt_path, domain: opt_domain});
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the names for all the cookies.
|
||||
* @return {!Array<string>} An array with the names of the cookies.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getKeys = function() {
|
||||
'use strict';
|
||||
return this.getKeyValues_().keys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values for all the cookies.
|
||||
* @return {!Array<string>} An array with the values of the cookies.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getValues = function() {
|
||||
'use strict';
|
||||
return this.getKeyValues_().values;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether there are any cookies for this document.
|
||||
*/
|
||||
goog.net.Cookies.prototype.isEmpty = function() {
|
||||
'use strict';
|
||||
return !this.getCookie_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of cookies for this document.
|
||||
*/
|
||||
goog.net.Cookies.prototype.getCount = function() {
|
||||
'use strict';
|
||||
const cookie = this.getCookie_();
|
||||
if (!cookie) {
|
||||
return 0;
|
||||
}
|
||||
return this.getParts_().length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is a cookie with the given name.
|
||||
* @param {string} key The name of the cookie to test for.
|
||||
* @return {boolean} Whether there is a cookie by that name.
|
||||
*/
|
||||
goog.net.Cookies.prototype.containsKey = function(key) {
|
||||
'use strict';
|
||||
// substring will return empty string if the key is not found, so the get
|
||||
// function will only return undefined
|
||||
return this.get(key) !== undefined;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is a cookie with the given value. (This is an O(n)
|
||||
* operation.)
|
||||
* @param {string} value The value to check for.
|
||||
* @return {boolean} Whether there is a cookie with that value.
|
||||
*/
|
||||
goog.net.Cookies.prototype.containsValue = function(value) {
|
||||
'use strict';
|
||||
// this O(n) in any case so lets do the trivial thing.
|
||||
const values = this.getKeyValues_().values;
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
if (values[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all cookies for this document. Note that this will only remove
|
||||
* cookies from the current path and domain. If there are cookies set using a
|
||||
* subpath and/or another domain these will still be there.
|
||||
*/
|
||||
goog.net.Cookies.prototype.clear = function() {
|
||||
'use strict';
|
||||
const keys = this.getKeyValues_().keys;
|
||||
for (let i = keys.length - 1; i >= 0; i--) {
|
||||
this.remove(keys[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to allow testing cookies without depending on the
|
||||
* browser.
|
||||
* @param {string} s The cookie string to set.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.setCookie_ = function(s) {
|
||||
'use strict';
|
||||
this.document_.cookie = s;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to allow testing cookies without depending on the
|
||||
* browser. IE6 can return null here.
|
||||
* @return {string} Returns the `document.cookie`.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getCookie_ = function() {
|
||||
'use strict';
|
||||
return this.document_.cookie;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Array<string>} The cookie split on semi colons.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getParts_ = function() {
|
||||
'use strict';
|
||||
return (this.getCookie_() || '').split(';');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the names and values for all the cookies.
|
||||
* @return {{keys:!Array<string>, values:!Array<string>}} An object with keys
|
||||
* and values.
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.prototype.getKeyValues_ = function() {
|
||||
'use strict';
|
||||
const parts = this.getParts_();
|
||||
const keys = [];
|
||||
const values = [];
|
||||
let index;
|
||||
let part;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
part = goog.string.trim(parts[i]);
|
||||
index = part.indexOf('=');
|
||||
|
||||
if (index == -1) { // empty name
|
||||
keys.push('');
|
||||
values.push(part);
|
||||
} else {
|
||||
keys.push(part.substring(0, index));
|
||||
values.push(part.substring(index + 1));
|
||||
}
|
||||
}
|
||||
return {keys: keys, values: values};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Options object for calls to Cookies.prototype.set.
|
||||
* @record
|
||||
*/
|
||||
goog.net.Cookies.SetOptions = function() {
|
||||
'use strict';
|
||||
/**
|
||||
* The max age in seconds (from now). Use -1 to set a session cookie. If not
|
||||
* provided, the default is -1 (i.e. set a session cookie).
|
||||
* @type {number|undefined}
|
||||
*/
|
||||
this.maxAge;
|
||||
/**
|
||||
* The path of the cookie. If not present then this uses the full request
|
||||
* path.
|
||||
* @type {?string|undefined}
|
||||
*/
|
||||
this.path;
|
||||
/**
|
||||
* The domain of the cookie, or null to not specify a domain attribute
|
||||
* (browser will use the full request host name). If not provided, the default
|
||||
* is null (i.e. let browser use full request host name).
|
||||
* @type {?string|undefined}
|
||||
*/
|
||||
this.domain;
|
||||
/**
|
||||
* Whether the cookie should only be sent over a secure channel.
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
this.secure;
|
||||
/**
|
||||
* The SameSite attribute for the cookie (default is NONE).
|
||||
* @type {!goog.net.Cookies.SameSite|undefined}
|
||||
*/
|
||||
this.sameSite;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Valid values for the SameSite cookie attribute. In 2019, browsers began the
|
||||
* process of changing the default from NONE to LAX.
|
||||
*
|
||||
* @see https://web.dev/samesite-cookies-explained
|
||||
* @see https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-5.3.7
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.net.Cookies.SameSite = {
|
||||
/**
|
||||
* The cookie will be sent in first-party contexts, including initial
|
||||
* navigation from external referrers.
|
||||
*/
|
||||
LAX: 'lax',
|
||||
/**
|
||||
* The cookie will be sent in all first-party or third-party contexts. This
|
||||
* was the original default behavior of the web, but will need to be set
|
||||
* explicitly starting in 2020.
|
||||
*/
|
||||
NONE: 'none',
|
||||
/**
|
||||
* The cookie will only be sent in first-party contexts. It will not be sent
|
||||
* on initial navigation from external referrers.
|
||||
*/
|
||||
STRICT: 'strict',
|
||||
};
|
||||
|
||||
/**
|
||||
* A static default instance.
|
||||
* @const {!goog.net.Cookies}
|
||||
* @private
|
||||
*/
|
||||
goog.net.Cookies.instance_ =
|
||||
new goog.net.Cookies(typeof document == 'undefined' ? null : document);
|
||||
|
||||
/**
|
||||
* Getter for the static instance of goog.net.Cookies.
|
||||
* @return {!goog.net.Cookies}
|
||||
*/
|
||||
goog.net.Cookies.getInstance = function() {
|
||||
'use strict';
|
||||
return goog.net.Cookies.instance_;
|
||||
};
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A static instance of goog.net.Cookies that uses the default
|
||||
* window document.
|
||||
* @deprecated use `goog.net.Cookies.getInstance()` instead.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.cookies');
|
||||
|
||||
goog.require('goog.net.Cookies');
|
||||
|
||||
// TODO(closure-team): This should be a singleton getter instead of a static
|
||||
// instance.
|
||||
/**
|
||||
* A static default instance.
|
||||
* @const {!goog.net.Cookies}
|
||||
*/
|
||||
goog.net.cookies = goog.net.Cookies.getInstance();
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Error codes shared between goog.net.IframeIo and
|
||||
* goog.net.XhrIo.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.ErrorCode');
|
||||
|
||||
|
||||
/**
|
||||
* Error codes
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.ErrorCode = {
|
||||
|
||||
/**
|
||||
* There is no error condition.
|
||||
*/
|
||||
NO_ERROR: 0,
|
||||
|
||||
/**
|
||||
* The most common error from iframeio, unfortunately, is that the browser
|
||||
* responded with an error page that is classed as a different domain. The
|
||||
* situations, are when a browser error page is shown -- 404, access denied,
|
||||
* DNS failure, connection reset etc.)
|
||||
*
|
||||
*/
|
||||
ACCESS_DENIED: 1,
|
||||
|
||||
/**
|
||||
* Currently the only case where file not found will be caused is when the
|
||||
* code is running on the local file system and a non-IE browser makes a
|
||||
* request to a file that doesn't exist.
|
||||
*/
|
||||
FILE_NOT_FOUND: 2,
|
||||
|
||||
/**
|
||||
* If Firefox shows a browser error page, such as a connection reset by
|
||||
* server or access denied, then it will fail silently without the error or
|
||||
* load handlers firing.
|
||||
*/
|
||||
FF_SILENT_ERROR: 3,
|
||||
|
||||
/**
|
||||
* Custom error provided by the client through the error check hook.
|
||||
*/
|
||||
CUSTOM_ERROR: 4,
|
||||
|
||||
/**
|
||||
* Exception was thrown while processing the request.
|
||||
*/
|
||||
EXCEPTION: 5,
|
||||
|
||||
/**
|
||||
* The Http response returned a non-successful http status code.
|
||||
*/
|
||||
HTTP_ERROR: 6,
|
||||
|
||||
/**
|
||||
* The request was aborted.
|
||||
*/
|
||||
ABORT: 7,
|
||||
|
||||
/**
|
||||
* The request timed out.
|
||||
*/
|
||||
TIMEOUT: 8,
|
||||
|
||||
/**
|
||||
* The resource is not available offline.
|
||||
*/
|
||||
OFFLINE: 9,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a friendly error message for an error code. These messages are for
|
||||
* debugging and are not localized.
|
||||
* @param {goog.net.ErrorCode} errorCode An error code.
|
||||
* @return {string} A message for debugging.
|
||||
*/
|
||||
goog.net.ErrorCode.getDebugMessage = function(errorCode) {
|
||||
'use strict';
|
||||
switch (errorCode) {
|
||||
case goog.net.ErrorCode.NO_ERROR:
|
||||
return 'No Error';
|
||||
|
||||
case goog.net.ErrorCode.ACCESS_DENIED:
|
||||
return 'Access denied to content document';
|
||||
|
||||
case goog.net.ErrorCode.FILE_NOT_FOUND:
|
||||
return 'File not found';
|
||||
|
||||
case goog.net.ErrorCode.FF_SILENT_ERROR:
|
||||
return 'Firefox silently errored';
|
||||
|
||||
case goog.net.ErrorCode.CUSTOM_ERROR:
|
||||
return 'Application custom error';
|
||||
|
||||
case goog.net.ErrorCode.EXCEPTION:
|
||||
return 'An exception occurred';
|
||||
|
||||
case goog.net.ErrorCode.HTTP_ERROR:
|
||||
return 'Http response at 400 or 500 level';
|
||||
|
||||
case goog.net.ErrorCode.ABORT:
|
||||
return 'Request was aborted';
|
||||
|
||||
case goog.net.ErrorCode.TIMEOUT:
|
||||
return 'Request timed out';
|
||||
|
||||
case goog.net.ErrorCode.OFFLINE:
|
||||
return 'The resource is not available offline';
|
||||
|
||||
default:
|
||||
return 'Unrecognized error code';
|
||||
}
|
||||
};
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Common events for the network classes.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.EventType');
|
||||
|
||||
|
||||
/**
|
||||
* Event names for network events
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.net.EventType = {
|
||||
COMPLETE: 'complete',
|
||||
SUCCESS: 'success',
|
||||
ERROR: 'error',
|
||||
ABORT: 'abort',
|
||||
READY: 'ready',
|
||||
READY_STATE_CHANGE: 'readystatechange',
|
||||
TIMEOUT: 'timeout',
|
||||
INCREMENTAL_DATA: 'incrementaldata',
|
||||
PROGRESS: 'progress',
|
||||
// DOWNLOAD_PROGRESS and UPLOAD_PROGRESS are special events dispatched by
|
||||
// goog.net.XhrIo to allow binding listeners specific to each type of
|
||||
// progress.
|
||||
DOWNLOAD_PROGRESS: 'downloadprogress',
|
||||
UPLOAD_PROGRESS: 'uploadprogress',
|
||||
};
|
||||
Executable
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Constants for HTTP status codes.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.HttpStatus');
|
||||
|
||||
|
||||
/**
|
||||
* HTTP Status Codes defined in RFC 2616, RFC 6585, RFC 4918 and RFC 7538.
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
* @see http://tools.ietf.org/html/rfc6585
|
||||
* @see https://tools.ietf.org/html/rfc4918
|
||||
* @see https://tools.ietf.org/html/rfc7538
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.HttpStatus = {
|
||||
// Informational 1xx
|
||||
CONTINUE: 100,
|
||||
SWITCHING_PROTOCOLS: 101,
|
||||
|
||||
// Successful 2xx
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
ACCEPTED: 202,
|
||||
NON_AUTHORITATIVE_INFORMATION: 203,
|
||||
NO_CONTENT: 204,
|
||||
RESET_CONTENT: 205,
|
||||
PARTIAL_CONTENT: 206,
|
||||
MULTI_STATUS: 207,
|
||||
|
||||
// Redirection 3xx
|
||||
MULTIPLE_CHOICES: 300,
|
||||
MOVED_PERMANENTLY: 301,
|
||||
FOUND: 302,
|
||||
SEE_OTHER: 303,
|
||||
NOT_MODIFIED: 304,
|
||||
USE_PROXY: 305,
|
||||
TEMPORARY_REDIRECT: 307,
|
||||
PERMANENT_REDIRECT: 308,
|
||||
|
||||
// Client Error 4xx
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
PAYMENT_REQUIRED: 402,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
METHOD_NOT_ALLOWED: 405,
|
||||
NOT_ACCEPTABLE: 406,
|
||||
PROXY_AUTHENTICATION_REQUIRED: 407,
|
||||
REQUEST_TIMEOUT: 408,
|
||||
CONFLICT: 409,
|
||||
GONE: 410,
|
||||
LENGTH_REQUIRED: 411,
|
||||
PRECONDITION_FAILED: 412,
|
||||
REQUEST_ENTITY_TOO_LARGE: 413,
|
||||
REQUEST_URI_TOO_LONG: 414,
|
||||
UNSUPPORTED_MEDIA_TYPE: 415,
|
||||
REQUEST_RANGE_NOT_SATISFIABLE: 416,
|
||||
EXPECTATION_FAILED: 417,
|
||||
UNPROCESSABLE_ENTITY: 422,
|
||||
LOCKED: 423,
|
||||
FAILED_DEPENDENCY: 424,
|
||||
PRECONDITION_REQUIRED: 428,
|
||||
TOO_MANY_REQUESTS: 429,
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
||||
CLIENT_CLOSED_REQUEST: 499, // Nonstandard, used by GRPC
|
||||
|
||||
// Server Error 5xx
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
NOT_IMPLEMENTED: 501,
|
||||
BAD_GATEWAY: 502,
|
||||
SERVICE_UNAVAILABLE: 503,
|
||||
GATEWAY_TIMEOUT: 504,
|
||||
HTTP_VERSION_NOT_SUPPORTED: 505,
|
||||
INSUFFICIENT_STORAGE: 507,
|
||||
NETWORK_AUTHENTICATION_REQUIRED: 511,
|
||||
|
||||
/*
|
||||
* IE returns this code for 204 due to its use of URLMon, which returns this
|
||||
* code for 'Operation Aborted'. The status text is 'Unknown', the response
|
||||
* headers are ''. Known to occur on IE 6 on XP through IE9 on Win7.
|
||||
*/
|
||||
QUIRK_IE_NO_CONTENT: 1223,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given status should be considered successful.
|
||||
*
|
||||
* Successful codes are OK (200), CREATED (201), ACCEPTED (202),
|
||||
* NO CONTENT (204), PARTIAL CONTENT (206), NOT MODIFIED (304),
|
||||
* and IE's no content code (1223).
|
||||
*
|
||||
* @param {number} status The status code to test.
|
||||
* @return {boolean} Whether the status code should be considered successful.
|
||||
*/
|
||||
goog.net.HttpStatus.isSuccess = function(status) {
|
||||
'use strict';
|
||||
switch (status) {
|
||||
case goog.net.HttpStatus.OK:
|
||||
case goog.net.HttpStatus.CREATED:
|
||||
case goog.net.HttpStatus.ACCEPTED:
|
||||
case goog.net.HttpStatus.NO_CONTENT:
|
||||
case goog.net.HttpStatus.PARTIAL_CONTENT:
|
||||
case goog.net.HttpStatus.NOT_MODIFIED:
|
||||
case goog.net.HttpStatus.QUIRK_IE_NO_CONTENT:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Implementation of XmlHttpFactory which allows construction from
|
||||
* simple factory methods.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.WrapperXmlHttpFactory');
|
||||
|
||||
/** @suppress {extraRequire} Typedef. */
|
||||
goog.require('goog.net.XhrLike');
|
||||
goog.require('goog.net.XmlHttpFactory');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An xhr factory subclass which can be constructed using two factory methods.
|
||||
* This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
|
||||
* with an unchanged signature.
|
||||
* @param {function():!goog.net.XhrLike.OrNative} xhrFactory
|
||||
* A function which returns a new XHR object.
|
||||
* @param {function():!Object} optionsFactory A function which returns the
|
||||
* options associated with xhr objects from this factory.
|
||||
* @extends {goog.net.XmlHttpFactory}
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
|
||||
'use strict';
|
||||
goog.net.XmlHttpFactory.call(this);
|
||||
|
||||
/**
|
||||
* XHR factory method.
|
||||
* @type {function() : !goog.net.XhrLike.OrNative}
|
||||
* @private
|
||||
*/
|
||||
this.xhrFactory_ = xhrFactory;
|
||||
|
||||
/**
|
||||
* Options factory method.
|
||||
* @type {function() : !Object}
|
||||
* @private
|
||||
*/
|
||||
this.optionsFactory_ = optionsFactory;
|
||||
};
|
||||
goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
|
||||
'use strict';
|
||||
return this.xhrFactory_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
|
||||
'use strict';
|
||||
return this.optionsFactory_();
|
||||
};
|
||||
Executable
+1461
File diff suppressed because it is too large
Load Diff
Executable
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Creates a pool of XhrIo objects to use. This allows multiple
|
||||
* XhrIo objects to be grouped together and requests will use next available
|
||||
* XhrIo object.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XhrIoPool');
|
||||
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.structs.PriorityPool');
|
||||
goog.requireType('goog.structs.Map');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A pool of XhrIo objects.
|
||||
* @param {goog.structs.Map=} opt_headers Map of default headers to add to every
|
||||
* request.
|
||||
* @param {number=} opt_minCount Minimum number of objects (Default: 0).
|
||||
* @param {number=} opt_maxCount Maximum number of objects (Default: 10).
|
||||
* @param {boolean=} opt_withCredentials Add credentials to every request
|
||||
* (Default: false).
|
||||
* @constructor
|
||||
* @extends {goog.structs.PriorityPool}
|
||||
*/
|
||||
goog.net.XhrIoPool = function(
|
||||
opt_headers, opt_minCount, opt_maxCount, opt_withCredentials) {
|
||||
'use strict';
|
||||
/**
|
||||
* Map of default headers to add to every request.
|
||||
* @type {goog.structs.Map|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.headers_ = opt_headers;
|
||||
|
||||
/**
|
||||
* Whether a "credentialed" requests are to be sent (ones that is aware of
|
||||
* cookies and authentication). This is applicable only for cross-domain
|
||||
* requests and more recent browsers that support this part of the HTTP Access
|
||||
* Control standard.
|
||||
*
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute
|
||||
*
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.withCredentials_ = !!opt_withCredentials;
|
||||
|
||||
// Must break convention of putting the super-class's constructor first. This
|
||||
// is because the super-class constructor calls adjustForMinMax, which calls
|
||||
// this class' createObject. In this class's implementation, it assumes that
|
||||
// there is a headers_, and will lack those if not yet present.
|
||||
goog.structs.PriorityPool.call(this, opt_minCount, opt_maxCount);
|
||||
};
|
||||
goog.inherits(goog.net.XhrIoPool, goog.structs.PriorityPool);
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of an XhrIo object to use in the pool.
|
||||
* @return {!goog.net.XhrIo} The created object.
|
||||
* @override
|
||||
*/
|
||||
goog.net.XhrIoPool.prototype.createObject = function() {
|
||||
'use strict';
|
||||
const xhrIo = new goog.net.XhrIo();
|
||||
const headers = this.headers_;
|
||||
if (headers) {
|
||||
headers.forEach(function(value, key) {
|
||||
'use strict';
|
||||
xhrIo.headers.set(key, value);
|
||||
});
|
||||
}
|
||||
if (this.withCredentials_) {
|
||||
xhrIo.setWithCredentials(true);
|
||||
}
|
||||
return xhrIo;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an object has become unusable and should not be used.
|
||||
* @param {Object} obj The object to test.
|
||||
* @return {boolean} Whether the object can be reused, which is true if the
|
||||
* object is not disposed and not active.
|
||||
* @override
|
||||
*/
|
||||
goog.net.XhrIoPool.prototype.objectCanBeReused = function(obj) {
|
||||
'use strict';
|
||||
// An active XhrIo object should never be used.
|
||||
const xhr = /** @type {goog.net.XhrIo} */ (obj);
|
||||
return !xhr.isDisposed() && !xhr.isActive();
|
||||
};
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XhrLike');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for the common parts of XMLHttpRequest.
|
||||
*
|
||||
* Mostly copied from externs/w3c_xml.js.
|
||||
*
|
||||
* @interface
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/
|
||||
*/
|
||||
goog.net.XhrLike = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Typedef that refers to either native or custom-implemented XHR objects.
|
||||
* @typedef {!goog.net.XhrLike|!XMLHttpRequest}
|
||||
*/
|
||||
goog.net.XhrLike.OrNative;
|
||||
|
||||
|
||||
/**
|
||||
* @type {function()|null|undefined}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange
|
||||
*/
|
||||
goog.net.XhrLike.prototype.onreadystatechange;
|
||||
|
||||
|
||||
/**
|
||||
* @type {?ArrayBuffer|?Blob|?Document|?Object|?string}
|
||||
* @see https://xhr.spec.whatwg.org/#response-object
|
||||
*/
|
||||
goog.net.XhrLike.prototype.response;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
|
||||
*/
|
||||
goog.net.XhrLike.prototype.responseText;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @see https://xhr.spec.whatwg.org/#the-responsetype-attribute
|
||||
*/
|
||||
goog.net.XhrLike.prototype.responseType;
|
||||
|
||||
|
||||
/**
|
||||
* @type {Document}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute
|
||||
*/
|
||||
goog.net.XhrLike.prototype.responseXML;
|
||||
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#readystate
|
||||
*/
|
||||
goog.net.XhrLike.prototype.readyState;
|
||||
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#status
|
||||
*/
|
||||
goog.net.XhrLike.prototype.status;
|
||||
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#statustext
|
||||
*/
|
||||
goog.net.XhrLike.prototype.statusText;
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} method
|
||||
* @param {string} url
|
||||
* @param {?boolean=} opt_async
|
||||
* @param {?string=} opt_user
|
||||
* @param {?string=} opt_password
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.open = function(
|
||||
method, url, opt_async, opt_user, opt_password) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.send = function(opt_data) {};
|
||||
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.abort = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} header
|
||||
* @param {string} value
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} header
|
||||
* @return {?string}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.getResponseHeader = function(header) {};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
* @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
|
||||
*/
|
||||
goog.net.XhrLike.prototype.getAllResponseHeaders = function() {};
|
||||
|
||||
/**
|
||||
* @type {?function(!TrustTokenAttributeType): void | undefined}
|
||||
* @see https://docs.google.com/document/d/1qUjtKgA7nMv9YGMhi0xWKEojkSITKzGLdIcZgoz6ZkI.
|
||||
*/
|
||||
goog.net.XhrLike.prototype.setTrustToken = function(trustTokenAttribute) {};
|
||||
Executable
+818
@@ -0,0 +1,818 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Manages a pool of XhrIo's. This handles all the details of
|
||||
* dealing with the XhrPool and provides a simple interface for sending requests
|
||||
* and managing events.
|
||||
*
|
||||
* This class supports queueing & prioritization of requests (XhrIoPool
|
||||
* handles this) and retrying of requests.
|
||||
*
|
||||
* The events fired by the XhrManager are an aggregation of the events of
|
||||
* each of its XhrIo objects (with some filtering, i.e., ERROR only called
|
||||
* when there are no more retries left). For this reason, all send requests have
|
||||
* to have an id, so that the user of this object can know which event is for
|
||||
* which request.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XhrManager');
|
||||
goog.provide('goog.net.XhrManager.Event');
|
||||
goog.provide('goog.net.XhrManager.Request');
|
||||
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.net.ErrorCode');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.net.XhrIoPool');
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
// TODO(user): Add some time in between retries.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A manager of an XhrIoPool.
|
||||
* @param {number=} opt_maxRetries Max. number of retries (Default: 1).
|
||||
* @param {goog.structs.Map=} opt_headers Map of default headers to add to every
|
||||
* request.
|
||||
* @param {number=} opt_minCount Min. number of objects (Default: 0).
|
||||
* @param {number=} opt_maxCount Max. number of objects (Default: 10).
|
||||
* @param {number=} opt_timeoutInterval Timeout (in ms) before aborting an
|
||||
* attempt (Default: 0ms).
|
||||
* @param {boolean=} opt_withCredentials Add credentials to every request
|
||||
* (Default: false).
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.net.XhrManager = function(
|
||||
opt_maxRetries, opt_headers, opt_minCount, opt_maxCount,
|
||||
opt_timeoutInterval, opt_withCredentials) {
|
||||
'use strict';
|
||||
goog.net.XhrManager.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Maximum number of retries for a given request
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.maxRetries_ = (opt_maxRetries !== undefined) ? opt_maxRetries : 1;
|
||||
|
||||
/**
|
||||
* Timeout interval for an attempt of a given request.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.timeoutInterval_ = (opt_timeoutInterval !== undefined) ?
|
||||
Math.max(0, opt_timeoutInterval) :
|
||||
0;
|
||||
|
||||
/**
|
||||
* Add credentials to every request.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.withCredentials_ = !!opt_withCredentials;
|
||||
|
||||
/**
|
||||
* The pool of XhrIo's to use.
|
||||
* @type {goog.net.XhrIoPool}
|
||||
* @private
|
||||
*/
|
||||
this.xhrPool_ = new goog.net.XhrIoPool(
|
||||
opt_headers, opt_minCount, opt_maxCount, opt_withCredentials);
|
||||
|
||||
/**
|
||||
* Map of ID's to requests.
|
||||
* @type {goog.structs.Map<string, !goog.net.XhrManager.Request>}
|
||||
* @private
|
||||
*/
|
||||
this.requests_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* The event handler.
|
||||
* @type {goog.events.EventHandler<!goog.net.XhrManager>}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
};
|
||||
goog.inherits(goog.net.XhrManager, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Error to throw when a send is attempted with an ID that the manager already
|
||||
* has registered for another request.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.ERROR_ID_IN_USE_ = '[goog.net.XhrManager] ID in use';
|
||||
|
||||
|
||||
/**
|
||||
* The goog.net.EventType's to listen/unlisten for on the XhrIo object.
|
||||
* @type {Array<goog.net.EventType>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.XHR_EVENT_TYPES_ = [
|
||||
goog.net.EventType.READY,
|
||||
goog.net.EventType.COMPLETE,
|
||||
goog.net.EventType.SUCCESS,
|
||||
goog.net.EventType.ERROR,
|
||||
goog.net.EventType.ABORT,
|
||||
goog.net.EventType.TIMEOUT,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of milliseconds after which an incomplete request will be
|
||||
* aborted. Zero means no timeout is set.
|
||||
* @param {number} ms Timeout interval in milliseconds; 0 means none.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.setTimeoutInterval = function(ms) {
|
||||
'use strict';
|
||||
this.timeoutInterval_ = Math.max(0, ms);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of requests either in flight, or waiting to be sent.
|
||||
* The count will include the current request if used within a COMPLETE event
|
||||
* handler or callback.
|
||||
* @return {number} The number of requests in flight or pending send.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.getOutstandingCount = function() {
|
||||
'use strict';
|
||||
return this.requests_.getCount();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of request ids that are either in flight, or waiting to
|
||||
* be sent. The id of the current request will be included if used within a
|
||||
* COMPLETE event handler or callback.
|
||||
* @return {!Array<string>} Request ids in flight or pending send.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.getOutstandingRequestIds = function() {
|
||||
'use strict';
|
||||
return this.requests_.getKeys();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Registers the given request to be sent. Throws an error if a request
|
||||
* already exists with the given ID.
|
||||
* NOTE: It is not sent immediately. It is buffered and will be sent when an
|
||||
* XhrIo object becomes available, taking into account the request's
|
||||
* priority. Note also that requests of equal priority are sent in an
|
||||
* implementation specific order - to get FIFO queue semantics use a
|
||||
* monotonically increasing priority for successive requests.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {string} url Uri to make the request to.
|
||||
* @param {string=} opt_method Send method, default: GET.
|
||||
* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
|
||||
* opt_content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
* @param {number=} opt_priority The priority of the request. A smaller value
|
||||
* means a higher priority.
|
||||
* @param {Function=} opt_callback Callback function for when request is
|
||||
* complete. The only param is the event object from the COMPLETE event.
|
||||
* @param {number=} opt_maxRetries The maximum number of times the request
|
||||
* should be retried.
|
||||
* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of
|
||||
* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.
|
||||
* @param {boolean=} opt_withCredentials Add credentials to this request,
|
||||
* default: false.
|
||||
* @return {!goog.net.XhrManager.Request} The queued request object.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.send = function(
|
||||
id, url, opt_method, opt_content, opt_headers, opt_priority, opt_callback,
|
||||
opt_maxRetries, opt_responseType, opt_withCredentials) {
|
||||
'use strict';
|
||||
const requests = this.requests_;
|
||||
// Check if there is already a request with the given id.
|
||||
if (requests.get(id)) {
|
||||
throw new Error(goog.net.XhrManager.ERROR_ID_IN_USE_);
|
||||
}
|
||||
|
||||
// Make the Request object.
|
||||
const request = new goog.net.XhrManager.Request(
|
||||
url, goog.bind(this.handleEvent_, this, id), opt_method, opt_content,
|
||||
opt_headers, opt_callback,
|
||||
opt_maxRetries !== undefined ? opt_maxRetries : this.maxRetries_,
|
||||
opt_responseType,
|
||||
opt_withCredentials !== undefined ? opt_withCredentials :
|
||||
this.withCredentials_);
|
||||
this.requests_.set(id, request);
|
||||
|
||||
// Setup the callback for the pool.
|
||||
const callback = goog.bind(this.handleAvailableXhr_, this, id);
|
||||
this.xhrPool_.getObject(callback, opt_priority);
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Aborts the request associated with id.
|
||||
* @param {string} id The id of the request to abort.
|
||||
* @param {boolean=} opt_force If true, remove the id now so it can be reused.
|
||||
* No events are fired and the callback is not called when forced.
|
||||
*/
|
||||
goog.net.XhrManager.prototype.abort = function(id, opt_force) {
|
||||
'use strict';
|
||||
const request = this.requests_.get(id);
|
||||
if (request) {
|
||||
const xhrIo = request.xhrIo;
|
||||
request.setAborted(true);
|
||||
if (opt_force) {
|
||||
if (xhrIo) {
|
||||
// We remove listeners to make sure nothing gets called if a new request
|
||||
// with the same id is made.
|
||||
this.removeXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
goog.events.listenOnce(xhrIo, goog.net.EventType.READY, function() {
|
||||
'use strict';
|
||||
this.xhrPool_.releaseObject(xhrIo);
|
||||
}, false, this);
|
||||
}
|
||||
this.requests_.remove(id);
|
||||
}
|
||||
if (xhrIo) {
|
||||
xhrIo.abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles when an XhrIo object becomes available. Sets up the events, fires
|
||||
* the READY event, and starts the process to send the request.
|
||||
* @param {string} id The id of the request the XhrIo is for.
|
||||
* @param {goog.net.XhrIo} xhrIo The available XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleAvailableXhr_ = function(id, xhrIo) {
|
||||
'use strict';
|
||||
const request = this.requests_.get(id);
|
||||
// Make sure the request doesn't already have an XhrIo attached. This can
|
||||
// happen if a forced abort occurs before an XhrIo is available, and a new
|
||||
// request with the same id is made.
|
||||
if (request && !request.xhrIo) {
|
||||
this.addXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
|
||||
// Set properties for the XhrIo.
|
||||
xhrIo.setTimeoutInterval(this.timeoutInterval_);
|
||||
xhrIo.setResponseType(request.getResponseType());
|
||||
xhrIo.setWithCredentials(request.getWithCredentials());
|
||||
|
||||
// Add a reference to the XhrIo object to the request.
|
||||
request.xhrIo = xhrIo;
|
||||
|
||||
// Notify the listeners.
|
||||
this.dispatchEvent(
|
||||
new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.READY, this, id, xhrIo));
|
||||
|
||||
// Send the request.
|
||||
this.retry_(id, xhrIo);
|
||||
|
||||
// If the request was aborted before it got an XhrIo object, abort it now.
|
||||
if (request.getAborted()) {
|
||||
xhrIo.abort();
|
||||
}
|
||||
} else {
|
||||
// If the request has an XhrIo object already, or no request exists, just
|
||||
// return the XhrIo back to the pool.
|
||||
this.xhrPool_.releaseObject(xhrIo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles all events fired by the XhrIo object for a given request.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.events.Event} e The event.
|
||||
* @return {Object} The return value from the handler, if any.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleEvent_ = function(id, e) {
|
||||
'use strict';
|
||||
const xhrIo = /** @type {goog.net.XhrIo} */ (e.target);
|
||||
switch (e.type) {
|
||||
case goog.net.EventType.READY:
|
||||
this.retry_(id, xhrIo);
|
||||
break;
|
||||
|
||||
case goog.net.EventType.COMPLETE:
|
||||
return this.handleComplete_(id, xhrIo, e);
|
||||
|
||||
case goog.net.EventType.SUCCESS:
|
||||
this.handleSuccess_(id, xhrIo);
|
||||
break;
|
||||
|
||||
// A timeout is handled like an error.
|
||||
case goog.net.EventType.TIMEOUT:
|
||||
case goog.net.EventType.ERROR:
|
||||
this.handleError_(id, xhrIo);
|
||||
break;
|
||||
|
||||
case goog.net.EventType.ABORT:
|
||||
this.handleAbort_(id, xhrIo);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to retry the given request. If the request has already attempted
|
||||
* the maximum number of retries, then it removes the request and releases
|
||||
* the XhrIo object back into the pool.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.retry_ = function(id, xhrIo) {
|
||||
'use strict';
|
||||
const request = this.requests_.get(id);
|
||||
|
||||
// If the request has not completed and it is below its max. retries.
|
||||
if (request && !request.getCompleted() && !request.hasReachedMaxRetries()) {
|
||||
request.increaseAttemptCount();
|
||||
xhrIo.send(
|
||||
request.getUrl(), request.getMethod(), request.getContent(),
|
||||
request.getHeaders());
|
||||
} else {
|
||||
if (request) {
|
||||
// Remove the events on the XhrIo objects.
|
||||
this.removeXhrListener_(xhrIo, request.getXhrEventCallback());
|
||||
|
||||
// Remove the request.
|
||||
this.requests_.remove(id);
|
||||
}
|
||||
// Release the XhrIo object back into the pool.
|
||||
this.xhrPool_.releaseObject(xhrIo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the complete of a request. Dispatches the COMPLETE event and sets the
|
||||
* the request as completed if the request has succeeded, or is done retrying.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @param {goog.events.Event} e The original event.
|
||||
* @return {Object} The return value from the callback, if any.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleComplete_ = function(id, xhrIo, e) {
|
||||
'use strict';
|
||||
// Only if the request is done processing should a COMPLETE event be fired.
|
||||
const request = this.requests_.get(id);
|
||||
if (xhrIo.getLastErrorCode() == goog.net.ErrorCode.ABORT ||
|
||||
xhrIo.isSuccess() || request.hasReachedMaxRetries()) {
|
||||
this.dispatchEvent(
|
||||
new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.COMPLETE, this, id, xhrIo));
|
||||
|
||||
// If the request exists, we mark it as completed and call the callback
|
||||
if (request) {
|
||||
request.setCompleted(true);
|
||||
// Call the complete callback as if it was set as a COMPLETE event on the
|
||||
// XhrIo directly.
|
||||
if (request.getCompleteCallback()) {
|
||||
return request.getCompleteCallback().call(xhrIo, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the abort of an underlying XhrIo object.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleAbort_ = function(id, xhrIo) {
|
||||
'use strict';
|
||||
// Fire event.
|
||||
// NOTE: The complete event should always be fired before the abort event, so
|
||||
// the bulk of the work is done in handleComplete.
|
||||
this.dispatchEvent(
|
||||
new goog.net.XhrManager.Event(goog.net.EventType.ABORT, this, id, xhrIo));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the success of a request. Dispatches the SUCCESS event and sets the
|
||||
* the request as completed.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleSuccess_ = function(id, xhrIo) {
|
||||
'use strict';
|
||||
// Fire event.
|
||||
// NOTE: We don't release the XhrIo object from the pool here.
|
||||
// It is released in the retry method, when we know it is back in the
|
||||
// ready state.
|
||||
this.dispatchEvent(
|
||||
new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.SUCCESS, this, id, xhrIo));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the error of a request. If the request has not reach its maximum
|
||||
* number of retries, then it lets the request retry naturally (will let the
|
||||
* request hit the READY state). Else, it dispatches the ERROR event.
|
||||
* @param {string} id The id of the request.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.handleError_ = function(id, xhrIo) {
|
||||
'use strict';
|
||||
const request = this.requests_.get(id);
|
||||
|
||||
// If the maximum number of retries has been reached.
|
||||
if (request.hasReachedMaxRetries()) {
|
||||
// Fire event.
|
||||
// NOTE: We don't release the XhrIo object from the pool here.
|
||||
// It is released in the retry method, when we know it is back in the
|
||||
// ready state.
|
||||
this.dispatchEvent(
|
||||
new goog.net.XhrManager.Event(
|
||||
goog.net.EventType.ERROR, this, id, xhrIo));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove listeners for XHR events on an XhrIo object.
|
||||
* @param {goog.net.XhrIo} xhrIo The object to stop listenening to events on.
|
||||
* @param {Function} func The callback to remove from event handling.
|
||||
* @param {string|Array<string>=} opt_types Event types to remove listeners
|
||||
* for. Defaults to XHR_EVENT_TYPES_.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.removeXhrListener_ = function(
|
||||
xhrIo, func, opt_types) {
|
||||
'use strict';
|
||||
const types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;
|
||||
this.eventHandler_.unlisten(xhrIo, types, func);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for XHR events on an XhrIo object.
|
||||
* @param {goog.net.XhrIo} xhrIo The object listen to events on.
|
||||
* @param {Function} func The callback when the event occurs.
|
||||
* @param {string|Array<string>=} opt_types Event types to attach listeners to.
|
||||
* Defaults to XHR_EVENT_TYPES_.
|
||||
* @private
|
||||
*/
|
||||
goog.net.XhrManager.prototype.addXhrListener_ = function(
|
||||
xhrIo, func, opt_types) {
|
||||
'use strict';
|
||||
const types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;
|
||||
this.eventHandler_.listen(xhrIo, types, func);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.XhrManager.prototype.disposeInternal = function() {
|
||||
'use strict';
|
||||
goog.net.XhrManager.superClass_.disposeInternal.call(this);
|
||||
|
||||
this.xhrPool_.dispose();
|
||||
this.xhrPool_ = null;
|
||||
|
||||
this.eventHandler_.dispose();
|
||||
this.eventHandler_ = null;
|
||||
|
||||
this.requests_.clear();
|
||||
this.requests_ = null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An event dispatched by XhrManager.
|
||||
*
|
||||
* @param {goog.net.EventType} type Event Type.
|
||||
* @param {goog.net.XhrManager} target Reference to the object that is the
|
||||
* target of this event.
|
||||
* @param {string} id The id of the request this event is for.
|
||||
* @param {goog.net.XhrIo} xhrIo The XhrIo object of the request.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
* @final
|
||||
*/
|
||||
goog.net.XhrManager.Event = function(type, target, id, xhrIo) {
|
||||
'use strict';
|
||||
goog.events.Event.call(this, type, target);
|
||||
|
||||
/**
|
||||
* The id of the request this event is for.
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The XhrIo object of the request.
|
||||
* @type {goog.net.XhrIo}
|
||||
*/
|
||||
this.xhrIo = xhrIo;
|
||||
};
|
||||
goog.inherits(goog.net.XhrManager.Event, goog.events.Event);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An encapsulation of everything needed to make a Xhr request.
|
||||
* NOTE: This is used internal to the XhrManager.
|
||||
*
|
||||
* @param {string} url Uri to make the request too.
|
||||
* @param {Function} xhrEventCallback Callback attached to the events of the
|
||||
* XhrIo object of the request.
|
||||
* @param {string=} opt_method Send method, default: GET.
|
||||
* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}
|
||||
* opt_content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
* @param {Function=} opt_callback Callback function for when request is
|
||||
* complete. NOTE: Only 1 callback supported across all events.
|
||||
* @param {number=} opt_maxRetries The maximum number of times the request
|
||||
* should be retried (Default: 1).
|
||||
* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of
|
||||
* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.
|
||||
* @param {boolean=} opt_withCredentials Add credentials to this request,
|
||||
* default: false.
|
||||
*
|
||||
* @constructor
|
||||
* @final
|
||||
*/
|
||||
goog.net.XhrManager.Request = function(
|
||||
url, xhrEventCallback, opt_method, opt_content, opt_headers, opt_callback,
|
||||
opt_maxRetries, opt_responseType, opt_withCredentials) {
|
||||
'use strict';
|
||||
/**
|
||||
* Uri to make the request too.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.url_ = url;
|
||||
|
||||
/**
|
||||
* Send method.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.method_ = opt_method || 'GET';
|
||||
|
||||
/**
|
||||
* Post data.
|
||||
* @type {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.content_ = opt_content;
|
||||
|
||||
/**
|
||||
* Map of headers
|
||||
* @type {Object|goog.structs.Map|null}
|
||||
* @private
|
||||
*/
|
||||
this.headers_ = opt_headers || null;
|
||||
|
||||
/**
|
||||
* The maximum number of times the request should be retried.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.maxRetries_ = (opt_maxRetries !== undefined) ? opt_maxRetries : 1;
|
||||
|
||||
/**
|
||||
* The number of attempts so far.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.attemptCount_ = 0;
|
||||
|
||||
/**
|
||||
* Whether the request has been completed.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.completed_ = false;
|
||||
|
||||
/**
|
||||
* Whether the request has been aborted.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.aborted_ = false;
|
||||
|
||||
/**
|
||||
* Callback attached to the events of the XhrIo object.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.xhrEventCallback_ = xhrEventCallback;
|
||||
|
||||
/**
|
||||
* Callback function called when request is complete.
|
||||
* @type {Function|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.completeCallback_ = opt_callback;
|
||||
|
||||
/**
|
||||
* A response type to set on this.xhrIo when it's populated.
|
||||
* @type {!goog.net.XhrIo.ResponseType}
|
||||
* @private
|
||||
*/
|
||||
this.responseType_ = opt_responseType || goog.net.XhrIo.ResponseType.DEFAULT;
|
||||
|
||||
/**
|
||||
* Send credentials with this request, or not.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.withCredentials_ = !!opt_withCredentials;
|
||||
|
||||
/**
|
||||
* The XhrIo instance handling this request. Set in handleAvailableXhr.
|
||||
* @type {?goog.net.XhrIo}
|
||||
*/
|
||||
this.xhrIo = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the uri.
|
||||
* @return {string} The uri to make the request to.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getUrl = function() {
|
||||
'use strict';
|
||||
return this.url_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the send method.
|
||||
* @return {string} The send method.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getMethod = function() {
|
||||
'use strict';
|
||||
return this.method_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the post data.
|
||||
* @return {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string|undefined}
|
||||
* The post data.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getContent = function() {
|
||||
'use strict';
|
||||
return this.content_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the map of headers.
|
||||
* @return {Object|goog.structs.Map} The map of headers.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getHeaders = function() {
|
||||
'use strict';
|
||||
return this.headers_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the withCredentials flag.
|
||||
* @return {boolean} Add credentials, or not.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getWithCredentials = function() {
|
||||
'use strict';
|
||||
return this.withCredentials_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the maximum number of times the request should be retried.
|
||||
* @return {number} The maximum number of times the request should be retried.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getMaxRetries = function() {
|
||||
'use strict';
|
||||
return this.maxRetries_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of attempts so far.
|
||||
* @return {number} The number of attempts so far.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getAttemptCount = function() {
|
||||
'use strict';
|
||||
return this.attemptCount_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Increases the number of attempts so far.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.increaseAttemptCount = function() {
|
||||
'use strict';
|
||||
this.attemptCount_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the request has reached the maximum number of retries.
|
||||
* @return {boolean} Whether the request has reached the maximum number of
|
||||
* retries.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.hasReachedMaxRetries = function() {
|
||||
'use strict';
|
||||
return this.attemptCount_ > this.maxRetries_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the completed status.
|
||||
* @param {boolean} complete The completed status.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.setCompleted = function(complete) {
|
||||
'use strict';
|
||||
this.completed_ = complete;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the completed status.
|
||||
* @return {boolean} The completed status.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getCompleted = function() {
|
||||
'use strict';
|
||||
return this.completed_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the aborted status.
|
||||
* @param {boolean} aborted True if the request was aborted, otherwise False.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.setAborted = function(aborted) {
|
||||
'use strict';
|
||||
this.aborted_ = aborted;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the aborted status.
|
||||
* @return {boolean} True if request was aborted, otherwise False.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getAborted = function() {
|
||||
'use strict';
|
||||
return this.aborted_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the callback attached to the events of the XhrIo object.
|
||||
* @return {Function} The callback attached to the events of the
|
||||
* XhrIo object.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getXhrEventCallback = function() {
|
||||
'use strict';
|
||||
return this.xhrEventCallback_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the callback for when the request is complete.
|
||||
* @return {Function|undefined} The callback for when the request is complete.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getCompleteCallback = function() {
|
||||
'use strict';
|
||||
return this.completeCallback_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the response type that will be set on this request's XhrIo when it's
|
||||
* available.
|
||||
* @return {!goog.net.XhrIo.ResponseType} The response type to be set
|
||||
* when an XhrIo becomes available to this request.
|
||||
*/
|
||||
goog.net.XhrManager.Request.prototype.getResponseType = function() {
|
||||
'use strict';
|
||||
return this.responseType_;
|
||||
};
|
||||
Executable
+250
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Low level handling of XMLHttpRequest.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.DefaultXmlHttpFactory');
|
||||
goog.provide('goog.net.XmlHttp');
|
||||
goog.provide('goog.net.XmlHttp.OptionType');
|
||||
goog.provide('goog.net.XmlHttp.ReadyState');
|
||||
goog.provide('goog.net.XmlHttpDefines');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.net.WrapperXmlHttpFactory');
|
||||
goog.require('goog.net.XmlHttpFactory');
|
||||
goog.requireType('goog.net.XhrLike');
|
||||
|
||||
|
||||
/**
|
||||
* Static class for creating XMLHttpRequest objects.
|
||||
* @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object.
|
||||
*/
|
||||
goog.net.XmlHttp = function() {
|
||||
'use strict';
|
||||
return goog.net.XmlHttp.factory_.createInstance();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
|
||||
* true bypasses the ActiveX probing code.
|
||||
* NOTE(ruilopes): Due to the way JSCompiler works, this define *will not* strip
|
||||
* out the ActiveX probing code from binaries. To achieve this, use
|
||||
* `goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR` instead.
|
||||
* TODO(ruilopes): Collapse both defines.
|
||||
*/
|
||||
goog.net.XmlHttp.ASSUME_NATIVE_XHR =
|
||||
goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
|
||||
|
||||
|
||||
/** @const */
|
||||
goog.net.XmlHttpDefines = {};
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
|
||||
* true eliminates the ActiveX probing code.
|
||||
*/
|
||||
goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR =
|
||||
goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false);
|
||||
|
||||
|
||||
/**
|
||||
* Gets the options to use with the XMLHttpRequest objects obtained using
|
||||
* the static methods.
|
||||
* @return {Object} The options.
|
||||
*/
|
||||
goog.net.XmlHttp.getOptions = function() {
|
||||
'use strict';
|
||||
return goog.net.XmlHttp.factory_.getOptions();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Type of options that an XmlHttp object can have.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.XmlHttp.OptionType = {
|
||||
/**
|
||||
* Whether a goog.nullFunction should be used to clear the onreadystatechange
|
||||
* handler instead of null.
|
||||
*/
|
||||
USE_NULL_FUNCTION: 0,
|
||||
|
||||
/**
|
||||
* NOTE(user): In IE if send() errors on a *local* request the readystate
|
||||
* is still changed to COMPLETE. We need to ignore it and allow the
|
||||
* try/catch around send() to pick up the error.
|
||||
*/
|
||||
LOCAL_REQUEST_ERROR: 1,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Status constants for XMLHTTP, matches:
|
||||
* https://msdn.microsoft.com/en-us/library/ms534361(v=vs.85).aspx
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.XmlHttp.ReadyState = {
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is uninitialized
|
||||
*/
|
||||
UNINITIALIZED: 0,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is loading.
|
||||
*/
|
||||
LOADING: 1,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is loaded.
|
||||
*/
|
||||
LOADED: 2,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is in an interactive state.
|
||||
*/
|
||||
INTERACTIVE: 3,
|
||||
|
||||
/**
|
||||
* Constant for when xmlhttprequest.readyState is completed
|
||||
*/
|
||||
COMPLETE: 4,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The global factory instance for creating XMLHttpRequest objects.
|
||||
* @type {goog.net.XmlHttpFactory}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XmlHttp.factory_;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the factories for creating XMLHttpRequest objects and their options.
|
||||
* @param {Function} factory The factory for XMLHttpRequest objects.
|
||||
* @param {Function} optionsFactory The factory for options.
|
||||
* @deprecated Use setGlobalFactory instead.
|
||||
*/
|
||||
goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
|
||||
'use strict';
|
||||
goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
|
||||
goog.asserts.assert(factory), goog.asserts.assert(optionsFactory)));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the global factory object.
|
||||
* @param {!goog.net.XmlHttpFactory} factory New global factory object.
|
||||
*/
|
||||
goog.net.XmlHttp.setGlobalFactory = function(factory) {
|
||||
'use strict';
|
||||
goog.net.XmlHttp.factory_ = factory;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default factory to use when creating xhr objects. You probably shouldn't be
|
||||
* instantiating this directly, but rather using it via goog.net.XmlHttp.
|
||||
* @extends {goog.net.XmlHttpFactory}
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory = function() {
|
||||
'use strict';
|
||||
goog.net.XmlHttpFactory.call(this);
|
||||
};
|
||||
goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
|
||||
'use strict';
|
||||
const progId = this.getProgId_();
|
||||
if (progId) {
|
||||
return new ActiveXObject(progId);
|
||||
} else {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
|
||||
'use strict';
|
||||
const progId = this.getProgId_();
|
||||
const options = {};
|
||||
if (progId) {
|
||||
options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
|
||||
options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the private state used by other functions.
|
||||
* @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
|
||||
* @private
|
||||
*/
|
||||
goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
|
||||
'use strict';
|
||||
if (goog.net.XmlHttp.ASSUME_NATIVE_XHR ||
|
||||
goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// The following blog post describes what PROG IDs to use to create the
|
||||
// XMLHTTP object in Internet Explorer:
|
||||
// http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
|
||||
// However we do not (yet) fully trust that this will be OK for old versions
|
||||
// of IE on Win9x so we therefore keep the last 2.
|
||||
if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
|
||||
typeof ActiveXObject != 'undefined') {
|
||||
// Candidate Active X types.
|
||||
const ACTIVE_X_IDENTS = [
|
||||
'MSXML2.XMLHTTP.6.0',
|
||||
'MSXML2.XMLHTTP.3.0',
|
||||
'MSXML2.XMLHTTP',
|
||||
'Microsoft.XMLHTTP',
|
||||
];
|
||||
for (let i = 0; i < ACTIVE_X_IDENTS.length; i++) {
|
||||
const candidate = ACTIVE_X_IDENTS[i];
|
||||
|
||||
try {
|
||||
new ActiveXObject(candidate);
|
||||
// NOTE(user): cannot assign progid and return candidate in one line
|
||||
// because JSCompiler complaings: BUG 658126
|
||||
this.ieProgId_ = candidate;
|
||||
return candidate;
|
||||
} catch (e) {
|
||||
// do nothing; try next choice
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't find any matches
|
||||
throw new Error(
|
||||
'Could not create ActiveXObject. ActiveX might be disabled,' +
|
||||
' or MSXML might not be installed');
|
||||
}
|
||||
|
||||
return /** @type {string} */ (this.ieProgId_);
|
||||
};
|
||||
|
||||
|
||||
// Set the global factory to an instance of the default factory.
|
||||
goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Interface for a factory for creating XMLHttpRequest objects
|
||||
* and metadata about them.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.XmlHttpFactory');
|
||||
|
||||
/** @suppress {extraRequire} Typedef. */
|
||||
goog.require('goog.net.XhrLike');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for an XmlHttpRequest factory.
|
||||
* @constructor
|
||||
*/
|
||||
goog.net.XmlHttpFactory = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Cache of options - we only actually call internalGetOptions once.
|
||||
* @type {?Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.net.XhrLike.OrNative} A new XhrLike instance.
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} Options describing how xhr objects obtained from this
|
||||
* factory should be used.
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.getOptions = function() {
|
||||
'use strict';
|
||||
return this.cachedOptions_ ||
|
||||
(this.cachedOptions_ = this.internalGetOptions());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Override this method in subclasses to preserve the caching offered by
|
||||
* getOptions().
|
||||
* @return {Object} Options describing how xhr objects obtained from this
|
||||
* factory should be used.
|
||||
* @protected
|
||||
*/
|
||||
goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
|
||||
Executable
+707
@@ -0,0 +1,707 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for manipulating objects/maps/hashes.
|
||||
*/
|
||||
goog.module('goog.object');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
/**
|
||||
* Calls a function for each element in an object/map/hash.
|
||||
* @param {?Object<K,V>} obj The object over which to iterate.
|
||||
* @param {function(this:T,V,?,?Object<K,V>):?} f The function to call for every
|
||||
* element. This function takes 3 arguments (the value, the key and the
|
||||
* object) and the return value is ignored.
|
||||
* @param {T=} opt_obj This is used as the 'this' object within f.
|
||||
* @return {void}
|
||||
* @template T,K,V
|
||||
*/
|
||||
function forEach(obj, f, opt_obj) {
|
||||
for (const key in obj) {
|
||||
f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function for each element in an object/map/hash. If that call returns
|
||||
* true, adds the element to a new object.
|
||||
* @param {?Object<K,V>} obj The object over which to iterate.
|
||||
* @param {function(this:T,V,?,?Object<K,V>):boolean} f The function to call for
|
||||
* every element. This function takes 3 arguments (the value, the key and
|
||||
* the object) and should return a boolean. If the return value is true the
|
||||
* element is added to the result object. If it is false the element is not
|
||||
* included.
|
||||
* @param {T=} opt_obj This is used as the 'this' object within f.
|
||||
* @return {!Object<K,V>} a new object in which only elements that passed the
|
||||
* test are present.
|
||||
* @template T,K,V
|
||||
*/
|
||||
function filter(obj, f, opt_obj) {
|
||||
const res = {};
|
||||
for (const key in obj) {
|
||||
if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
|
||||
res[key] = obj[key];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* For every element in an object/map/hash calls a function and inserts the
|
||||
* result into a new object.
|
||||
* @param {?Object<K,V>} obj The object over which to iterate.
|
||||
* @param {function(this:T,V,?,?Object<K,V>):R} f The function to call for every
|
||||
* element. This function takes 3 arguments (the value, the key and the
|
||||
* object) and should return something. The result will be inserted into a
|
||||
* new object.
|
||||
* @param {T=} opt_obj This is used as the 'this' object within f.
|
||||
* @return {!Object<K,R>} a new object with the results from f.
|
||||
* @template T,K,V,R
|
||||
*/
|
||||
function map(obj, f, opt_obj) {
|
||||
const res = {};
|
||||
for (const key in obj) {
|
||||
res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function for each element in an object/map/hash. If any
|
||||
* call returns true, returns true (without checking the rest). If
|
||||
* all calls return false, returns false.
|
||||
* @param {?Object<K,V>} obj The object to check.
|
||||
* @param {function(this:T,V,?,?Object<K,V>):boolean} f The function to call for
|
||||
* every element. This function takes 3 arguments (the value, the key and
|
||||
* the object) and should return a boolean.
|
||||
* @param {T=} opt_obj This is used as the 'this' object within f.
|
||||
* @return {boolean} true if any element passes the test.
|
||||
* @template T,K,V
|
||||
*/
|
||||
function some(obj, f, opt_obj) {
|
||||
for (const key in obj) {
|
||||
if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function for each element in an object/map/hash. If
|
||||
* all calls return true, returns true. If any call returns false, returns
|
||||
* false at this point and does not continue to check the remaining elements.
|
||||
* @param {?Object<K,V>} obj The object to check.
|
||||
* @param {?function(this:T,V,?,?Object<K,V>):boolean} f The function to call
|
||||
* for every element. This function takes 3 arguments (the value, the key
|
||||
* and the object) and should return a boolean.
|
||||
* @param {T=} opt_obj This is used as the 'this' object within f.
|
||||
* @return {boolean} false if any element fails the test.
|
||||
* @template T,K,V
|
||||
*/
|
||||
function every(obj, f, opt_obj) {
|
||||
for (const key in obj) {
|
||||
if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of key-value pairs in the object map.
|
||||
* @param {?Object} obj The object for which to get the number of key-value
|
||||
* pairs.
|
||||
* @return {number} The number of key-value pairs in the object map.
|
||||
*/
|
||||
function getCount(obj) {
|
||||
let rv = 0;
|
||||
for (const key in obj) {
|
||||
rv++;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one key from the object map, if any exists.
|
||||
* For map literals the returned key will be the first one in most of the
|
||||
* browsers (a know exception is Konqueror).
|
||||
* @param {?Object} obj The object to pick a key from.
|
||||
* @return {string|undefined} The key or undefined if the object is empty.
|
||||
*/
|
||||
function getAnyKey(obj) {
|
||||
for (const key in obj) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one value from the object map, if any exists.
|
||||
* For map literals the returned value will be the first one in most of the
|
||||
* browsers (a know exception is Konqueror).
|
||||
* @param {?Object<K,V>} obj The object to pick a value from.
|
||||
* @return {V|undefined} The value or undefined if the object is empty.
|
||||
* @template K,V
|
||||
*/
|
||||
function getAnyValue(obj) {
|
||||
for (const key in obj) {
|
||||
return obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the object/hash/map contains the given object as a value.
|
||||
* An alias for containsValue(obj, val).
|
||||
* @param {?Object<K,V>} obj The object in which to look for val.
|
||||
* @param {V} val The object for which to check.
|
||||
* @return {boolean} true if val is present.
|
||||
* @template K,V
|
||||
*/
|
||||
function contains(obj, val) {
|
||||
return containsValue(obj, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of the object/map/hash.
|
||||
* @param {?Object<K,V>} obj The object from which to get the values.
|
||||
* @return {!Array<V>} The values in the object/map/hash.
|
||||
* @template K,V
|
||||
*/
|
||||
function getValues(obj) {
|
||||
const res = [];
|
||||
let i = 0;
|
||||
for (const key in obj) {
|
||||
res[i++] = obj[key];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keys of the object/map/hash.
|
||||
* @param {?Object} obj The object from which to get the keys.
|
||||
* @return {!Array<string>} Array of property keys.
|
||||
*/
|
||||
function getKeys(obj) {
|
||||
const res = [];
|
||||
let i = 0;
|
||||
for (const key in obj) {
|
||||
res[i++] = key;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from an object multiple levels deep. This is useful for
|
||||
* pulling values from deeply nested objects, such as JSON responses.
|
||||
* Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
|
||||
* @param {?Object} obj An object to get the value from. Can be array-like.
|
||||
* @param {...(string|number|!IArrayLike<number|string>)} var_args A number of
|
||||
* keys (as strings, or numbers, for array-like objects). Can also be
|
||||
* specified as a single array of keys.
|
||||
* @return {*} The resulting value. If, at any point, the value for a key in the
|
||||
* current object is null or undefined, returns undefined.
|
||||
*/
|
||||
function getValueByKeys(obj, var_args) {
|
||||
const isArrayLike = goog.isArrayLike(var_args);
|
||||
const keys = isArrayLike ?
|
||||
/** @type {!IArrayLike<number|string>} */ (var_args) :
|
||||
arguments;
|
||||
|
||||
// Start with the 2nd parameter for the variable parameters syntax.
|
||||
for (let i = isArrayLike ? 0 : 1; i < keys.length; i++) {
|
||||
if (obj == null) return undefined;
|
||||
obj = obj[keys[i]];
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the object/map/hash contains the given key.
|
||||
* @param {?Object} obj The object in which to look for key.
|
||||
* @param {?} key The key for which to check.
|
||||
* @return {boolean} true If the map contains the key.
|
||||
*/
|
||||
function containsKey(obj, key) {
|
||||
return obj !== null && key in obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the object/map/hash contains the given value. This is O(n).
|
||||
* @param {?Object<K,V>} obj The object in which to look for val.
|
||||
* @param {V} val The value for which to check.
|
||||
* @return {boolean} true If the map contains the value.
|
||||
* @template K,V
|
||||
*/
|
||||
function containsValue(obj, val) {
|
||||
for (const key in obj) {
|
||||
if (obj[key] == val) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches an object for an element that satisfies the given condition and
|
||||
* returns its key.
|
||||
* @param {?Object<K,V>} obj The object to search in.
|
||||
* @param {function(this:T,V,string,?Object<K,V>):boolean} f The function to
|
||||
* call for every element. Takes 3 arguments (the value, the key and the
|
||||
* object) and should return a boolean.
|
||||
* @param {T=} thisObj An optional "this" context for the function.
|
||||
* @return {string|undefined} The key of an element for which the function
|
||||
* returns true or undefined if no such element is found.
|
||||
* @template T,K,V
|
||||
*/
|
||||
function findKey(obj, f, thisObj = undefined) {
|
||||
for (const key in obj) {
|
||||
if (f.call(/** @type {?} */ (thisObj), obj[key], key, obj)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches an object for an element that satisfies the given condition and
|
||||
* returns its value.
|
||||
* @param {?Object<K,V>} obj The object to search in.
|
||||
* @param {function(this:T,V,string,?Object<K,V>):boolean} f The function to
|
||||
* call for every element. Takes 3 arguments (the value, the key and the
|
||||
* object) and should return a boolean.
|
||||
* @param {T=} thisObj An optional "this" context for the function.
|
||||
* @return {V} The value of an element for which the function returns true or
|
||||
* undefined if no such element is found.
|
||||
* @template T,K,V
|
||||
*/
|
||||
function findValue(obj, f, thisObj = undefined) {
|
||||
const key = findKey(obj, f, thisObj);
|
||||
return key && obj[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the object/map/hash is empty.
|
||||
* @param {?Object} obj The object to test.
|
||||
* @return {boolean} true if obj is empty.
|
||||
*/
|
||||
function isEmpty(obj) {
|
||||
for (const key in obj) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all key value pairs from the object/map/hash.
|
||||
* @param {?Object} obj The object to clear.
|
||||
* @return {void}
|
||||
*/
|
||||
function clear(obj) {
|
||||
for (const i in obj) {
|
||||
delete obj[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a key-value pair based on the key.
|
||||
* @param {?Object} obj The object from which to remove the key.
|
||||
* @param {?} key The key to remove.
|
||||
* @return {boolean} Whether an element was removed.
|
||||
*/
|
||||
function remove(obj, key) {
|
||||
let rv;
|
||||
if (rv = key in /** @type {!Object} */ (obj)) {
|
||||
delete obj[key];
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the object. Throws an exception if the key is
|
||||
* already in use. Use set if you want to change an existing pair.
|
||||
* @param {?Object<K,V>} obj The object to which to add the key-value pair.
|
||||
* @param {string} key The key to add.
|
||||
* @param {V} val The value to add.
|
||||
* @return {void}
|
||||
* @template K,V
|
||||
*/
|
||||
function add(obj, key, val) {
|
||||
if (obj !== null && key in obj) {
|
||||
throw new Error(`The object already contains the key "${key}"`);
|
||||
}
|
||||
set(obj, key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the given key.
|
||||
* @param {?Object<K,V>} obj The object from which to get the value.
|
||||
* @param {string} key The key for which to get the value.
|
||||
* @param {R=} val The value to return if no item is found for the given key
|
||||
* (default is undefined).
|
||||
* @return {V|R|undefined} The value for the given key.
|
||||
* @template K,V,R
|
||||
*/
|
||||
function get(obj, key, val = undefined) {
|
||||
if (obj !== null && key in obj) {
|
||||
return obj[key];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the object/map/hash.
|
||||
* @param {?Object<K,V>} obj The object to which to add the key-value pair.
|
||||
* @param {string} key The key to add.
|
||||
* @param {V} value The value to add.
|
||||
* @template K,V
|
||||
* @return {void}
|
||||
*/
|
||||
function set(obj, key, value) {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the object/map/hash if it doesn't exist yet.
|
||||
* @param {?Object<K,V>} obj The object to which to add the key-value pair.
|
||||
* @param {string} key The key to add.
|
||||
* @param {V} value The value to add if the key wasn't present.
|
||||
* @return {V} The value of the entry at the end of the function.
|
||||
* @template K,V
|
||||
*/
|
||||
function setIfUndefined(obj, key, value) {
|
||||
return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a key and value to an object if the key is not set. The value will be
|
||||
* the return value of the given function. If the key already exists, the
|
||||
* object will not be changed and the function will not be called (the function
|
||||
* will be lazily evaluated -- only called if necessary).
|
||||
* This function is particularly useful when used with an `Object` which is
|
||||
* acting as a cache.
|
||||
* @param {?Object<K,V>} obj The object to which to add the key-value pair.
|
||||
* @param {string} key The key to add.
|
||||
* @param {function():V} f The value to add if the key wasn't present.
|
||||
* @return {V} The value of the entry at the end of the function.
|
||||
* @template K,V
|
||||
*/
|
||||
function setWithReturnValueIfNotSet(obj, key, f) {
|
||||
if (key in obj) {
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
const val = f();
|
||||
obj[key] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two objects for equality using === on the values.
|
||||
* @param {!Object<K,V>} a
|
||||
* @param {!Object<K,V>} b
|
||||
* @return {boolean}
|
||||
* @template K,V
|
||||
*/
|
||||
function equals(a, b) {
|
||||
for (const k in a) {
|
||||
if (!(k in b) || a[k] !== b[k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const k in b) {
|
||||
if (!(k in a)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow clone of the object.
|
||||
* @param {?Object<K,V>} obj Object to clone.
|
||||
* @return {!Object<K,V>} Clone of the input object.
|
||||
* @template K,V
|
||||
*/
|
||||
function clone(obj) {
|
||||
const res = {};
|
||||
for (const key in obj) {
|
||||
res[key] = obj[key];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a value. The input may be an Object, Array, or basic type. Objects and
|
||||
* arrays will be cloned recursively.
|
||||
* WARNINGS:
|
||||
* <code>unsafeClone</code> does not detect reference loops. Objects
|
||||
* that refer to themselves will cause infinite recursion.
|
||||
* <code>unsafeClone</code> is unaware of unique identifiers, and
|
||||
* copies UIDs created by <code>getUid</code> into cloned results.
|
||||
* @param {T} obj The value to clone.
|
||||
* @return {T} A clone of the input value.
|
||||
* @template T
|
||||
*/
|
||||
function unsafeClone(obj) {
|
||||
if (!obj || typeof obj !== 'object') return obj;
|
||||
if (typeof obj.clone === 'function') return obj.clone();
|
||||
if (typeof Map !== 'undefined' && obj instanceof Map) {
|
||||
return new Map(obj);
|
||||
} else if (typeof Set !== 'undefined' && obj instanceof Set) {
|
||||
return new Set(obj);
|
||||
}
|
||||
const clone = Array.isArray(obj) ? [] :
|
||||
typeof ArrayBuffer === 'function' &&
|
||||
typeof ArrayBuffer.isView === 'function' && ArrayBuffer.isView(obj) &&
|
||||
!(obj instanceof DataView) ?
|
||||
new obj.constructor(obj.length) :
|
||||
{};
|
||||
for (const key in obj) {
|
||||
clone[key] = unsafeClone(obj[key]);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new object in which all the keys and values are interchanged
|
||||
* (keys become values and values become keys). If multiple keys map to the
|
||||
* same value, the chosen transposed value is implementation-dependent.
|
||||
* @param {?Object} obj The object to transpose.
|
||||
* @return {!Object} The transposed object.
|
||||
*/
|
||||
function transpose(obj) {
|
||||
const transposed = {};
|
||||
for (const key in obj) {
|
||||
transposed[obj[key]] = key;
|
||||
}
|
||||
return transposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* The names of the fields that are defined on Object.prototype.
|
||||
* @type {!Array<string>}
|
||||
*/
|
||||
const PROTOTYPE_FIELDS = [
|
||||
'constructor',
|
||||
'hasOwnProperty',
|
||||
'isPrototypeOf',
|
||||
'propertyIsEnumerable',
|
||||
'toLocaleString',
|
||||
'toString',
|
||||
'valueOf',
|
||||
];
|
||||
|
||||
/**
|
||||
* Extends an object with another object.
|
||||
* This operates 'in-place'; it does not create a new Object.
|
||||
* Example:
|
||||
* var o = {};
|
||||
* extend(o, {a: 0, b: 1});
|
||||
* o; // {a: 0, b: 1}
|
||||
* extend(o, {b: 2, c: 3});
|
||||
* o; // {a: 0, b: 2, c: 3}
|
||||
* @param {?Object} target The object to modify. Existing properties will be
|
||||
* overwritten if they are also present in one of the objects in `var_args`.
|
||||
* @param {...(?Object|undefined)} var_args The objects from which values
|
||||
* will be copied.
|
||||
* @return {void}
|
||||
* @deprecated Prefer Object.assign
|
||||
*/
|
||||
function extend(target, var_args) {
|
||||
let key;
|
||||
let source;
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
source = arguments[i];
|
||||
for (key in source) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
|
||||
// For IE the for-in-loop does not contain any properties that are not
|
||||
// enumerable on the prototype object (for example isPrototypeOf from
|
||||
// Object.prototype) and it will also not include 'replace' on objects that
|
||||
// extend String and change 'replace' (not that it is common for anyone to
|
||||
// extend anything except Object).
|
||||
|
||||
for (let j = 0; j < PROTOTYPE_FIELDS.length; j++) {
|
||||
key = PROTOTYPE_FIELDS[j];
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object built from the key-value pairs provided as arguments.
|
||||
* @param {...*} var_args If only one argument is provided and it is an array
|
||||
* then this is used as the arguments, otherwise even arguments are used as
|
||||
* the property names and odd arguments are used as the property values.
|
||||
* @return {!Object} The new object.
|
||||
* @throws {!Error} If there are uneven number of arguments or there is only one
|
||||
* non array argument.
|
||||
*/
|
||||
function create(var_args) {
|
||||
const argLength = arguments.length;
|
||||
if (argLength == 1 && Array.isArray(arguments[0])) {
|
||||
return create.apply(null, arguments[0]);
|
||||
}
|
||||
|
||||
if (argLength % 2) {
|
||||
throw new Error('Uneven number of arguments');
|
||||
}
|
||||
|
||||
const rv = {};
|
||||
for (let i = 0; i < argLength; i += 2) {
|
||||
rv[arguments[i]] = arguments[i + 1];
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object where the property names come from the arguments but
|
||||
* the value is always set to true
|
||||
* @param {...*} var_args If only one argument is provided and it is an array
|
||||
* then this is used as the arguments, otherwise the arguments are used as
|
||||
* the property names.
|
||||
* @return {!Object} The new object.
|
||||
*/
|
||||
function createSet(var_args) {
|
||||
const argLength = arguments.length;
|
||||
if (argLength == 1 && Array.isArray(arguments[0])) {
|
||||
return createSet.apply(null, arguments[0]);
|
||||
}
|
||||
|
||||
const rv = {};
|
||||
for (let i = 0; i < argLength; i++) {
|
||||
rv[arguments[i]] = true;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an immutable view of the underlying object, if the browser
|
||||
* supports immutable objects.
|
||||
* In default mode, writes to this view will fail silently. In strict mode,
|
||||
* they will throw an error.
|
||||
* @param {!Object<K,V>} obj An object.
|
||||
* @return {!Object<K,V>} An immutable view of that object, or the original
|
||||
* object if this browser does not support immutables.
|
||||
* @template K,V
|
||||
*/
|
||||
function createImmutableView(obj) {
|
||||
let result = obj;
|
||||
if (Object.isFrozen && !Object.isFrozen(obj)) {
|
||||
result = Object.create(obj);
|
||||
Object.freeze(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Object} obj An object.
|
||||
* @return {boolean} Whether this is an immutable view of the object.
|
||||
*/
|
||||
function isImmutableView(obj) {
|
||||
return !!Object.isFrozen && Object.isFrozen(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all properties names on a given Object regardless of enumerability.
|
||||
* <p> If the browser does not support `Object.getOwnPropertyNames` nor
|
||||
* `Object.getPrototypeOf` then this is equivalent to using
|
||||
* `getKeys`
|
||||
* @param {?Object} obj The object to get the properties of.
|
||||
* @param {boolean=} includeObjectPrototype Whether properties defined on
|
||||
* `Object.prototype` should be included in the result.
|
||||
* @param {boolean=} includeFunctionPrototype Whether properties defined on
|
||||
* `Function.prototype` should be included in the result.
|
||||
* @return {!Array<string>}
|
||||
* @public
|
||||
*/
|
||||
function getAllPropertyNames(
|
||||
obj, includeObjectPrototype = undefined,
|
||||
includeFunctionPrototype = undefined) {
|
||||
if (!obj) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Naively use a for..in loop to get the property names if the browser doesn't
|
||||
// support any other APIs for getting it.
|
||||
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||
return getKeys(obj);
|
||||
}
|
||||
|
||||
const visitedSet = {};
|
||||
|
||||
// Traverse the prototype chain and add all properties to the visited set.
|
||||
let proto = obj;
|
||||
while (proto && (proto !== Object.prototype || !!includeObjectPrototype) &&
|
||||
(proto !== Function.prototype || !!includeFunctionPrototype)) {
|
||||
const names = Object.getOwnPropertyNames(proto);
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
visitedSet[names[i]] = true;
|
||||
}
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
|
||||
return getKeys(visitedSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a ES5 or ES6 class reference, return its super class / super
|
||||
* constructor.
|
||||
* This should be used in rare cases where you need to walk up the inheritance
|
||||
* tree (this is generally a bad idea). But this work with ES5 and ES6 classes,
|
||||
* unlike relying on the superClass_ property.
|
||||
* Note: To start walking up the hierarchy from an instance call this with its
|
||||
* `constructor` property; e.g. `getSuperClass(instance.constructor)`.
|
||||
* @param {function(new: ?)} constructor
|
||||
* @return {?Object}
|
||||
*/
|
||||
function getSuperClass(constructor) {
|
||||
const proto = Object.getPrototypeOf(constructor.prototype);
|
||||
return proto && proto.constructor;
|
||||
}
|
||||
|
||||
exports = {
|
||||
add,
|
||||
clear,
|
||||
clone,
|
||||
contains,
|
||||
containsKey,
|
||||
containsValue,
|
||||
create,
|
||||
createImmutableView,
|
||||
createSet,
|
||||
equals,
|
||||
every,
|
||||
extend,
|
||||
filter,
|
||||
findKey,
|
||||
findValue,
|
||||
forEach,
|
||||
get,
|
||||
getAllPropertyNames,
|
||||
getAnyKey,
|
||||
getAnyValue,
|
||||
getCount,
|
||||
getKeys,
|
||||
getSuperClass,
|
||||
getValueByKeys,
|
||||
getValues,
|
||||
isEmpty,
|
||||
isImmutableView,
|
||||
map,
|
||||
remove,
|
||||
set,
|
||||
setIfUndefined,
|
||||
setWithReturnValueIfNotSet,
|
||||
some,
|
||||
transpose,
|
||||
unsafeClone,
|
||||
};
|
||||
Executable
+1447
File diff suppressed because it is too large
Load Diff
Executable
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.promise.Resolver');
|
||||
|
||||
goog.requireType('goog.Promise');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Resolver interface for promises. The resolver is a convenience interface that
|
||||
* bundles the promise and its associated resolve and reject functions together,
|
||||
* for cases where the resolver needs to be persisted internally.
|
||||
*
|
||||
* @interface
|
||||
* @template TYPE
|
||||
*/
|
||||
goog.promise.Resolver = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* The promise that created this resolver.
|
||||
* @type {!goog.Promise<TYPE>}
|
||||
*/
|
||||
goog.promise.Resolver.prototype.promise;
|
||||
|
||||
|
||||
/**
|
||||
* Resolves this resolver with the specified value.
|
||||
* @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)}
|
||||
*/
|
||||
goog.promise.Resolver.prototype.resolve;
|
||||
|
||||
|
||||
/**
|
||||
* Rejects this resolver with the specified reason.
|
||||
* @type {function(*=): void}
|
||||
*/
|
||||
goog.promise.Resolver.prototype.reject;
|
||||
Executable
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.Thenable');
|
||||
|
||||
/** @suppress {extraRequire} used in complex type */
|
||||
goog.requireType('goog.Promise'); // for the type reference.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a more strict interface for Thenables in terms of
|
||||
* http://promisesaplus.com for interop with {@see goog.Promise}.
|
||||
*
|
||||
* @interface
|
||||
* @extends {IThenable<TYPE>}
|
||||
* @template TYPE
|
||||
*/
|
||||
goog.Thenable = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Adds callbacks that will operate on the result of the Thenable, returning a
|
||||
* new child Promise.
|
||||
*
|
||||
* If the Thenable is fulfilled, the `onFulfilled` callback will be
|
||||
* invoked with the fulfillment value as argument, and the child Promise will
|
||||
* be fulfilled with the return value of the callback. If the callback throws
|
||||
* an exception, the child Promise will be rejected with the thrown value
|
||||
* instead.
|
||||
*
|
||||
* If the Thenable is rejected, the `onRejected` callback will be invoked with
|
||||
* the rejection reason as argument. Similar to the fulfilled case, the child
|
||||
* Promise will then be resolved with the return value of the callback, or
|
||||
* rejected with the thrown value if the callback throws an exception.
|
||||
*
|
||||
* @param {?(function(this:THIS, TYPE): VALUE)=} opt_onFulfilled A
|
||||
* function that will be invoked with the fulfillment value if the Promise
|
||||
* is fulfilled.
|
||||
* @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
|
||||
* be invoked with the rejection reason if the Promise is rejected.
|
||||
* @param {THIS=} opt_context An optional context object that will be the
|
||||
* execution context for the callbacks. By default, functions are executed
|
||||
* with the default this.
|
||||
*
|
||||
* @return {RESULT} A new Promise that will receive the result
|
||||
* of the fulfillment or rejection callback.
|
||||
* @template VALUE
|
||||
* @template THIS
|
||||
*
|
||||
* When a Promise (or thenable) is returned from the fulfilled callback,
|
||||
* the result is the payload of that promise, not the promise itself.
|
||||
*
|
||||
* @template RESULT := type('goog.Promise',
|
||||
* cond(isUnknown(VALUE), unknown(),
|
||||
* mapunion(VALUE, (V) =>
|
||||
* cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
|
||||
* templateTypeOf(V, 0),
|
||||
* cond(sub(V, 'Thenable'),
|
||||
* unknown(),
|
||||
* V)))))
|
||||
* =:
|
||||
*
|
||||
*/
|
||||
goog.Thenable.prototype.then = function(
|
||||
opt_onFulfilled, opt_onRejected, opt_context) {};
|
||||
|
||||
|
||||
/**
|
||||
* An expando property to indicate that an object implements
|
||||
* `goog.Thenable`.
|
||||
*
|
||||
* {@see addImplementation}.
|
||||
*
|
||||
* @const
|
||||
*/
|
||||
goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable';
|
||||
|
||||
|
||||
/**
|
||||
* Marks a given class (constructor) as an implementation of Thenable, so
|
||||
* that we can query that fact at runtime. The class must have already
|
||||
* implemented the interface.
|
||||
* Exports a 'then' method on the constructor prototype, so that the objects
|
||||
* also implement the extern {@see goog.Thenable} interface for interop with
|
||||
* other Promise implementations.
|
||||
* @param {function(new:goog.Thenable,...?)} ctor The class constructor. The
|
||||
* corresponding class must have already implemented the interface.
|
||||
*/
|
||||
goog.Thenable.addImplementation = function(ctor) {
|
||||
'use strict';
|
||||
if (COMPILED) {
|
||||
ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true;
|
||||
} else {
|
||||
// Avoids dictionary access in uncompiled mode.
|
||||
ctor.prototype.$goog_Thenable = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?} object
|
||||
* @return {boolean} Whether a given instance implements `goog.Thenable`.
|
||||
* The class/superclass of the instance must call `addImplementation`.
|
||||
*/
|
||||
goog.Thenable.isImplementedBy = function(object) {
|
||||
'use strict';
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (COMPILED) {
|
||||
return !!object[goog.Thenable.IMPLEMENTED_BY_PROP];
|
||||
}
|
||||
return !!object.$goog_Thenable;
|
||||
} catch (e) {
|
||||
// Property access seems to be forbidden.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Useful compiler idioms.
|
||||
*/
|
||||
|
||||
goog.provide('goog.reflect');
|
||||
|
||||
|
||||
/**
|
||||
* Syntax for object literal casts.
|
||||
* @see http://go/jscompiler-renaming
|
||||
* @see https://goo.gl/CRs09P
|
||||
*
|
||||
* Use this if you have an object literal whose keys need to have the same names
|
||||
* as the properties of some class even after they are renamed by the compiler.
|
||||
*
|
||||
* @param {!Function} type Type to cast to.
|
||||
* @param {Object} object Object literal to cast.
|
||||
* @return {Object} The object literal.
|
||||
*/
|
||||
goog.reflect.object = function(type, object) {
|
||||
'use strict';
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Syntax for renaming property strings.
|
||||
* @see http://go/jscompiler-renaming
|
||||
* @see https://goo.gl/CRs09P
|
||||
*
|
||||
* Use this if you have an need to access a property as a string, but want
|
||||
* to also have the property renamed by the compiler. In contrast to
|
||||
* goog.reflect.object, this method takes an instance of an object.
|
||||
*
|
||||
* Properties must be simple names (not qualified names).
|
||||
*
|
||||
* @param {string} prop Name of the property
|
||||
* @param {!Object} object Instance of the object whose type will be used
|
||||
* for renaming
|
||||
* @return {string} The renamed property.
|
||||
*/
|
||||
goog.reflect.objectProperty = function(prop, object) {
|
||||
'use strict';
|
||||
return prop;
|
||||
};
|
||||
|
||||
/**
|
||||
* To assert to the compiler that an operation is needed when it would
|
||||
* otherwise be stripped. For example:
|
||||
* <code>
|
||||
* // Force a layout
|
||||
* goog.reflect.sinkValue(dialog.offsetHeight);
|
||||
* </code>
|
||||
* @param {T} x
|
||||
* @return {T}
|
||||
* @template T
|
||||
*/
|
||||
goog.reflect.sinkValue = function(x) {
|
||||
'use strict';
|
||||
goog.reflect.sinkValue[' '](x);
|
||||
return x;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The compiler should optimize this function away iff no one ever uses
|
||||
* goog.reflect.sinkValue.
|
||||
*/
|
||||
goog.reflect.sinkValue[' '] = goog.nullFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Check if a property can be accessed without throwing an exception.
|
||||
* @param {Object} obj The owner of the property.
|
||||
* @param {string} prop The property name.
|
||||
* @return {boolean} Whether the property is accessible. Will also return true
|
||||
* if obj is null.
|
||||
*/
|
||||
goog.reflect.canAccessProperty = function(obj, prop) {
|
||||
'use strict';
|
||||
try {
|
||||
goog.reflect.sinkValue(obj[prop]);
|
||||
return true;
|
||||
} catch (e) {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a value from a cache given a key. The compiler provides special
|
||||
* consideration for this call such that it is generally considered side-effect
|
||||
* free. However, if the `opt_keyFn` or `valueFn` have side-effects
|
||||
* then the entire call is considered to have side-effects.
|
||||
*
|
||||
* Conventionally storing the value on the cache would be considered a
|
||||
* side-effect and preclude unused calls from being pruned, ie. even if
|
||||
* the value was never used, it would still always be stored in the cache.
|
||||
*
|
||||
* Providing a side-effect free `valueFn` and `opt_keyFn`
|
||||
* allows unused calls to `goog.reflect.cache` to be pruned.
|
||||
*
|
||||
* @param {!Object<K, V>} cacheObj The object that contains the cached values.
|
||||
* @param {?} key The key to lookup in the cache. If it is not string or number
|
||||
* then a `opt_keyFn` should be provided. The key is also used as the
|
||||
* parameter to the `valueFn`.
|
||||
* @param {function(?):V} valueFn The value provider to use to calculate the
|
||||
* value to store in the cache. This function should be side-effect free
|
||||
* to take advantage of the optimization.
|
||||
* @param {function(?):K=} opt_keyFn The key provider to determine the cache
|
||||
* map key. This should be used if the given key is not a string or number.
|
||||
* If not provided then the given key is used. This function should be
|
||||
* side-effect free to take advantage of the optimization.
|
||||
* @return {V} The cached or calculated value.
|
||||
* @template K
|
||||
* @template V
|
||||
*/
|
||||
goog.reflect.cache = function(cacheObj, key, valueFn, opt_keyFn) {
|
||||
'use strict';
|
||||
const storedKey = opt_keyFn ? opt_keyFn(key) : key;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(cacheObj, storedKey)) {
|
||||
return cacheObj[storedKey];
|
||||
}
|
||||
|
||||
return (cacheObj[storedKey] = valueFn(key));
|
||||
};
|
||||
Executable
+186
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.string.Const');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.string.TypedString');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for compile-time-constant strings.
|
||||
*
|
||||
* Const is a wrapper for strings that can only be created from program
|
||||
* constants (i.e., string literals). This property relies on a custom Closure
|
||||
* compiler check that `goog.string.Const.from` is only invoked on
|
||||
* compile-time-constant expressions.
|
||||
*
|
||||
* Const is useful in APIs whose correct and secure use requires that certain
|
||||
* arguments are not attacker controlled: Compile-time constants are inherently
|
||||
* under the control of the application and not under control of external
|
||||
* attackers, and hence are safe to use in such contexts.
|
||||
*
|
||||
* Instances of this type must be created via its factory method
|
||||
* `goog.string.Const.from` and not by invoking its constructor. The
|
||||
* constructor intentionally takes no parameters and the type is immutable;
|
||||
* hence only a default instance corresponding to the empty string can be
|
||||
* obtained via constructor invocation. Use goog.string.Const.EMPTY
|
||||
* instead of using this constructor to get an empty Const string.
|
||||
*
|
||||
* @see goog.string.Const#from
|
||||
* @constructor
|
||||
* @final
|
||||
* @struct
|
||||
* @implements {goog.string.TypedString}
|
||||
* @param {Object=} opt_token package-internal implementation detail.
|
||||
* @param {string=} opt_content package-internal implementation detail.
|
||||
*/
|
||||
goog.string.Const = function(opt_token, opt_content) {
|
||||
'use strict';
|
||||
/**
|
||||
* The wrapped value of this Const object. The field has a purposely ugly
|
||||
* name to make (non-compiled) code that attempts to directly access this
|
||||
* field stand out.
|
||||
* @private {string}
|
||||
*/
|
||||
this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ =
|
||||
((opt_token ===
|
||||
goog.string.Const.GOOG_STRING_CONSTRUCTOR_TOKEN_PRIVATE_) &&
|
||||
opt_content) ||
|
||||
'';
|
||||
|
||||
/**
|
||||
* A type marker used to implement additional run-time type checking.
|
||||
* @see goog.string.Const#unwrap
|
||||
* @const {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ =
|
||||
goog.string.Const.TYPE_MARKER_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @const
|
||||
*/
|
||||
goog.string.Const.prototype.implementsGoogStringTypedString = true;
|
||||
|
||||
|
||||
/**
|
||||
* Returns this Const's value as a string.
|
||||
*
|
||||
* IMPORTANT: In code where it is security-relevant that an object's type is
|
||||
* indeed `goog.string.Const`, use `goog.string.Const.unwrap`
|
||||
* instead of this method.
|
||||
*
|
||||
* @see goog.string.Const#unwrap
|
||||
* @override
|
||||
* @return {string}
|
||||
*/
|
||||
goog.string.Const.prototype.getTypedStringValue = function() {
|
||||
'use strict';
|
||||
return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
|
||||
};
|
||||
|
||||
|
||||
if (goog.DEBUG) {
|
||||
/**
|
||||
* Returns a debug-string representation of this value.
|
||||
*
|
||||
* To obtain the actual string value wrapped inside an object of this type,
|
||||
* use `goog.string.Const.unwrap`.
|
||||
*
|
||||
* @see goog.string.Const#unwrap
|
||||
* @override
|
||||
* @return {string}
|
||||
*/
|
||||
goog.string.Const.prototype.toString = function() {
|
||||
'use strict';
|
||||
return 'Const{' +
|
||||
this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
|
||||
'}';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs a runtime check that the provided object is indeed an instance
|
||||
* of `goog.string.Const`, and returns its value.
|
||||
* @param {!goog.string.Const} stringConst The object to extract from.
|
||||
* @return {string} The Const object's contained string, unless the run-time
|
||||
* type check fails. In that case, `unwrap` returns an innocuous
|
||||
* string, or, if assertions are enabled, throws
|
||||
* `goog.asserts.AssertionError`.
|
||||
*/
|
||||
goog.string.Const.unwrap = function(stringConst) {
|
||||
'use strict';
|
||||
// Perform additional run-time type-checking to ensure that stringConst is
|
||||
// indeed an instance of the expected type. This provides some additional
|
||||
// protection against security bugs due to application code that disables type
|
||||
// checks.
|
||||
if (stringConst instanceof goog.string.Const &&
|
||||
stringConst.constructor === goog.string.Const &&
|
||||
stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ ===
|
||||
goog.string.Const.TYPE_MARKER_) {
|
||||
return stringConst
|
||||
.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
|
||||
} else {
|
||||
goog.asserts.fail(
|
||||
'expected object of type Const, got \'' + stringConst + '\'');
|
||||
return 'type_error:Const';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Const object from a compile-time constant string.
|
||||
*
|
||||
* It is illegal to invoke this function on an expression whose
|
||||
* compile-time-constant value cannot be determined by the Closure compiler.
|
||||
*
|
||||
* Correct invocations include,
|
||||
* <pre>
|
||||
* var s = goog.string.Const.from('hello');
|
||||
* var t = goog.string.Const.from('hello' + 'world');
|
||||
* </pre>
|
||||
*
|
||||
* In contrast, the following are illegal:
|
||||
* <pre>
|
||||
* var s = goog.string.Const.from(getHello());
|
||||
* var t = goog.string.Const.from('hello' + world);
|
||||
* </pre>
|
||||
*
|
||||
* @param {string} s A constant string from which to create a Const.
|
||||
* @return {!goog.string.Const} A Const object initialized to stringConst.
|
||||
*/
|
||||
goog.string.Const.from = function(s) {
|
||||
'use strict';
|
||||
return new goog.string.Const(
|
||||
goog.string.Const.GOOG_STRING_CONSTRUCTOR_TOKEN_PRIVATE_, s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Type marker for the Const type, used to implement additional run-time
|
||||
* type checking.
|
||||
* @const {!Object}
|
||||
* @private
|
||||
*/
|
||||
goog.string.Const.TYPE_MARKER_ = {};
|
||||
|
||||
/**
|
||||
* @type {!Object}
|
||||
* @private
|
||||
* @const
|
||||
*/
|
||||
goog.string.Const.GOOG_STRING_CONSTRUCTOR_TOKEN_PRIVATE_ = {};
|
||||
|
||||
/**
|
||||
* A Const instance wrapping the empty string.
|
||||
* @const {!goog.string.Const}
|
||||
*/
|
||||
goog.string.Const.EMPTY = goog.string.Const.from('');
|
||||
Executable
+394
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview String functions called from Closure packages that couldn't
|
||||
* depend on each other. Outside Closure, use goog.string function which
|
||||
* delegate to these.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.string.internal');
|
||||
|
||||
|
||||
/**
|
||||
* Fast prefix-checker.
|
||||
* @param {string} str The string to check.
|
||||
* @param {string} prefix A string to look for at the start of `str`.
|
||||
* @return {boolean} True if `str` begins with `prefix`.
|
||||
* @see goog.string.startsWith
|
||||
*/
|
||||
goog.string.internal.startsWith = function(str, prefix) {
|
||||
'use strict';
|
||||
return str.lastIndexOf(prefix, 0) == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fast suffix-checker.
|
||||
* @param {string} str The string to check.
|
||||
* @param {string} suffix A string to look for at the end of `str`.
|
||||
* @return {boolean} True if `str` ends with `suffix`.
|
||||
* @see goog.string.endsWith
|
||||
*/
|
||||
goog.string.internal.endsWith = function(str, suffix) {
|
||||
'use strict';
|
||||
const l = str.length - suffix.length;
|
||||
return l >= 0 && str.indexOf(suffix, l) == l;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Case-insensitive prefix-checker.
|
||||
* @param {string} str The string to check.
|
||||
* @param {string} prefix A string to look for at the end of `str`.
|
||||
* @return {boolean} True if `str` begins with `prefix` (ignoring
|
||||
* case).
|
||||
* @see goog.string.caseInsensitiveStartsWith
|
||||
*/
|
||||
goog.string.internal.caseInsensitiveStartsWith = function(str, prefix) {
|
||||
'use strict';
|
||||
return goog.string.internal.caseInsensitiveCompare(
|
||||
prefix, str.substr(0, prefix.length)) == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Case-insensitive suffix-checker.
|
||||
* @param {string} str The string to check.
|
||||
* @param {string} suffix A string to look for at the end of `str`.
|
||||
* @return {boolean} True if `str` ends with `suffix` (ignoring
|
||||
* case).
|
||||
* @see goog.string.caseInsensitiveEndsWith
|
||||
*/
|
||||
goog.string.internal.caseInsensitiveEndsWith = function(str, suffix) {
|
||||
'use strict';
|
||||
return (
|
||||
goog.string.internal.caseInsensitiveCompare(
|
||||
suffix, str.substr(str.length - suffix.length, suffix.length)) == 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Case-insensitive equality checker.
|
||||
* @param {string} str1 First string to check.
|
||||
* @param {string} str2 Second string to check.
|
||||
* @return {boolean} True if `str1` and `str2` are the same string,
|
||||
* ignoring case.
|
||||
* @see goog.string.caseInsensitiveEquals
|
||||
*/
|
||||
goog.string.internal.caseInsensitiveEquals = function(str1, str2) {
|
||||
'use strict';
|
||||
return str1.toLowerCase() == str2.toLowerCase();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is empty or contains only whitespaces.
|
||||
* @param {string} str The string to check.
|
||||
* @return {boolean} Whether `str` is empty or whitespace only.
|
||||
* @see goog.string.isEmptyOrWhitespace
|
||||
*/
|
||||
goog.string.internal.isEmptyOrWhitespace = function(str) {
|
||||
'use strict';
|
||||
// testing length == 0 first is actually slower in all browsers (about the
|
||||
// same in Opera).
|
||||
// Since IE doesn't include non-breaking-space (0xa0) in their \s character
|
||||
// class (as required by section 7.2 of the ECMAScript spec), we explicitly
|
||||
// include it in the regexp to enforce consistent cross-browser behavior.
|
||||
return /^[\s\xa0]*$/.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Trims white spaces to the left and right of a string.
|
||||
* @param {string} str The string to trim.
|
||||
* @return {string} A trimmed copy of `str`.
|
||||
*/
|
||||
goog.string.internal.trim =
|
||||
(goog.TRUSTED_SITE && String.prototype.trim) ? function(str) {
|
||||
'use strict';
|
||||
return str.trim();
|
||||
} : function(str) {
|
||||
'use strict';
|
||||
// Since IE doesn't include non-breaking-space (0xa0) in their \s
|
||||
// character class (as required by section 7.2 of the ECMAScript spec),
|
||||
// we explicitly include it in the regexp to enforce consistent
|
||||
// cross-browser behavior.
|
||||
// NOTE: We don't use String#replace because it might have side effects
|
||||
// causing this function to not compile to 0 bytes.
|
||||
return /^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(str)[1];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A string comparator that ignores case.
|
||||
* -1 = str1 less than str2
|
||||
* 0 = str1 equals str2
|
||||
* 1 = str1 greater than str2
|
||||
*
|
||||
* @param {string} str1 The string to compare.
|
||||
* @param {string} str2 The string to compare `str1` to.
|
||||
* @return {number} The comparator result, as described above.
|
||||
* @see goog.string.caseInsensitiveCompare
|
||||
*/
|
||||
goog.string.internal.caseInsensitiveCompare = function(str1, str2) {
|
||||
'use strict';
|
||||
const test1 = String(str1).toLowerCase();
|
||||
const test2 = String(str2).toLowerCase();
|
||||
|
||||
if (test1 < test2) {
|
||||
return -1;
|
||||
} else if (test1 == test2) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts \n to <br>s or <br />s.
|
||||
* @param {string} str The string in which to convert newlines.
|
||||
* @param {boolean=} opt_xml Whether to use XML compatible tags.
|
||||
* @return {string} A copy of `str` with converted newlines.
|
||||
* @see goog.string.newLineToBr
|
||||
*/
|
||||
goog.string.internal.newLineToBr = function(str, opt_xml) {
|
||||
'use strict';
|
||||
return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Escapes double quote '"' and single quote '\'' characters in addition to
|
||||
* '&', '<', and '>' so that a string can be included in an HTML tag attribute
|
||||
* value within double or single quotes.
|
||||
* @param {string} str string to be escaped.
|
||||
* @param {boolean=} opt_isLikelyToContainHtmlChars
|
||||
* @return {string} An escaped copy of `str`.
|
||||
* @see goog.string.htmlEscape
|
||||
*/
|
||||
goog.string.internal.htmlEscape = function(
|
||||
str, opt_isLikelyToContainHtmlChars) {
|
||||
'use strict';
|
||||
if (opt_isLikelyToContainHtmlChars) {
|
||||
str = str.replace(goog.string.internal.AMP_RE_, '&')
|
||||
.replace(goog.string.internal.LT_RE_, '<')
|
||||
.replace(goog.string.internal.GT_RE_, '>')
|
||||
.replace(goog.string.internal.QUOT_RE_, '"')
|
||||
.replace(goog.string.internal.SINGLE_QUOTE_RE_, ''')
|
||||
.replace(goog.string.internal.NULL_RE_, '�');
|
||||
return str;
|
||||
|
||||
} else {
|
||||
// quick test helps in the case when there are no chars to replace, in
|
||||
// worst case this makes barely a difference to the time taken
|
||||
if (!goog.string.internal.ALL_RE_.test(str)) return str;
|
||||
|
||||
// str.indexOf is faster than regex.test in this case
|
||||
if (str.indexOf('&') != -1) {
|
||||
str = str.replace(goog.string.internal.AMP_RE_, '&');
|
||||
}
|
||||
if (str.indexOf('<') != -1) {
|
||||
str = str.replace(goog.string.internal.LT_RE_, '<');
|
||||
}
|
||||
if (str.indexOf('>') != -1) {
|
||||
str = str.replace(goog.string.internal.GT_RE_, '>');
|
||||
}
|
||||
if (str.indexOf('"') != -1) {
|
||||
str = str.replace(goog.string.internal.QUOT_RE_, '"');
|
||||
}
|
||||
if (str.indexOf('\'') != -1) {
|
||||
str = str.replace(goog.string.internal.SINGLE_QUOTE_RE_, ''');
|
||||
}
|
||||
if (str.indexOf('\x00') != -1) {
|
||||
str = str.replace(goog.string.internal.NULL_RE_, '�');
|
||||
}
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches an ampersand, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.AMP_RE_ = /&/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches a less than sign, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.LT_RE_ = /</g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches a greater than sign, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.GT_RE_ = />/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches a double quote, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.QUOT_RE_ = /"/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches a single quote, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.SINGLE_QUOTE_RE_ = /'/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches null character, for use in escaping.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.NULL_RE_ = /\x00/g;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches any character that needs to be escaped.
|
||||
* @const {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.ALL_RE_ = /[\x00&<>"']/;
|
||||
|
||||
|
||||
/**
|
||||
* Do escaping of whitespace to preserve spatial formatting. We use character
|
||||
* entity #160 to make it safer for xml.
|
||||
* @param {string} str The string in which to escape whitespace.
|
||||
* @param {boolean=} opt_xml Whether to use XML compatible tags.
|
||||
* @return {string} An escaped copy of `str`.
|
||||
* @see goog.string.whitespaceEscape
|
||||
*/
|
||||
goog.string.internal.whitespaceEscape = function(str, opt_xml) {
|
||||
'use strict';
|
||||
// This doesn't use goog.string.preserveSpaces for backwards compatibility.
|
||||
return goog.string.internal.newLineToBr(
|
||||
str.replace(/ /g, '  '), opt_xml);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a string contains a substring.
|
||||
* @param {string} str The string to search.
|
||||
* @param {string} subString The substring to search for.
|
||||
* @return {boolean} Whether `str` contains `subString`.
|
||||
* @see goog.string.contains
|
||||
*/
|
||||
goog.string.internal.contains = function(str, subString) {
|
||||
'use strict';
|
||||
return str.indexOf(subString) != -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a string contains a substring, ignoring case.
|
||||
* @param {string} str The string to search.
|
||||
* @param {string} subString The substring to search for.
|
||||
* @return {boolean} Whether `str` contains `subString`.
|
||||
* @see goog.string.caseInsensitiveContains
|
||||
*/
|
||||
goog.string.internal.caseInsensitiveContains = function(str, subString) {
|
||||
'use strict';
|
||||
return goog.string.internal.contains(
|
||||
str.toLowerCase(), subString.toLowerCase());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares two version numbers.
|
||||
*
|
||||
* @param {string|number} version1 Version of first item.
|
||||
* @param {string|number} version2 Version of second item.
|
||||
*
|
||||
* @return {number} 1 if `version1` is higher.
|
||||
* 0 if arguments are equal.
|
||||
* -1 if `version2` is higher.
|
||||
* @see goog.string.compareVersions
|
||||
*/
|
||||
goog.string.internal.compareVersions = function(version1, version2) {
|
||||
'use strict';
|
||||
let order = 0;
|
||||
// Trim leading and trailing whitespace and split the versions into
|
||||
// subversions.
|
||||
const v1Subs = goog.string.internal.trim(String(version1)).split('.');
|
||||
const v2Subs = goog.string.internal.trim(String(version2)).split('.');
|
||||
const subCount = Math.max(v1Subs.length, v2Subs.length);
|
||||
|
||||
// Iterate over the subversions, as long as they appear to be equivalent.
|
||||
for (let subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
|
||||
let v1Sub = v1Subs[subIdx] || '';
|
||||
let v2Sub = v2Subs[subIdx] || '';
|
||||
|
||||
do {
|
||||
// Split the subversions into pairs of numbers and qualifiers (like 'b').
|
||||
// Two different RegExp objects are use to make it clear the code
|
||||
// is side-effect free
|
||||
const v1Comp = /(\d*)(\D*)(.*)/.exec(v1Sub) || ['', '', '', ''];
|
||||
const v2Comp = /(\d*)(\D*)(.*)/.exec(v2Sub) || ['', '', '', ''];
|
||||
// Break if there are no more matches.
|
||||
if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse the numeric part of the subversion. A missing number is
|
||||
// equivalent to 0.
|
||||
const v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
|
||||
const v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
|
||||
|
||||
// Compare the subversion components. The number has the highest
|
||||
// precedence. Next, if the numbers are equal, a subversion without any
|
||||
// qualifier is always higher than a subversion with any qualifier. Next,
|
||||
// the qualifiers are compared as strings.
|
||||
order = goog.string.internal.compareElements_(v1CompNum, v2CompNum) ||
|
||||
goog.string.internal.compareElements_(
|
||||
v1Comp[2].length == 0, v2Comp[2].length == 0) ||
|
||||
goog.string.internal.compareElements_(v1Comp[2], v2Comp[2]);
|
||||
// Stop as soon as an inequality is discovered.
|
||||
|
||||
v1Sub = v1Comp[3];
|
||||
v2Sub = v2Comp[3];
|
||||
} while (order == 0);
|
||||
}
|
||||
|
||||
return order;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares elements of a version number.
|
||||
*
|
||||
* @param {string|number|boolean} left An element from a version number.
|
||||
* @param {string|number|boolean} right An element from a version number.
|
||||
*
|
||||
* @return {number} 1 if `left` is higher.
|
||||
* 0 if arguments are equal.
|
||||
* -1 if `right` is higher.
|
||||
* @private
|
||||
*/
|
||||
goog.string.internal.compareElements_ = function(left, right) {
|
||||
'use strict';
|
||||
if (left < right) {
|
||||
return -1;
|
||||
} else if (left > right) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
Executable
+1512
File diff suppressed because it is too large
Load Diff
Executable
+101
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utility for fast string concatenation.
|
||||
*/
|
||||
|
||||
goog.provide('goog.string.StringBuffer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Utility class to facilitate string concatenation.
|
||||
*
|
||||
* @param {*=} opt_a1 Optional first initial item to append.
|
||||
* @param {...*} var_args Other initial items to
|
||||
* append, e.g., new goog.string.StringBuffer('foo', 'bar').
|
||||
* @constructor
|
||||
*/
|
||||
goog.string.StringBuffer = function(opt_a1, var_args) {
|
||||
'use strict';
|
||||
if (opt_a1 != null) {
|
||||
this.append.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal buffer for the string to be concatenated.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.buffer_ = '';
|
||||
|
||||
|
||||
/**
|
||||
* Sets the contents of the string buffer object, replacing what's currently
|
||||
* there.
|
||||
*
|
||||
* @param {*} s String to set.
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.set = function(s) {
|
||||
'use strict';
|
||||
this.buffer_ = '' + s;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Appends one or more items to the buffer.
|
||||
*
|
||||
* Calling this with null, undefined, or empty arguments is an error.
|
||||
*
|
||||
* @param {*} a1 Required first string.
|
||||
* @param {*=} opt_a2 Optional second string.
|
||||
* @param {...?} var_args Other items to append,
|
||||
* e.g., sb.append('foo', 'bar', 'baz').
|
||||
* @return {!goog.string.StringBuffer} This same StringBuffer object.
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
|
||||
'use strict';
|
||||
// Use a1 directly to avoid arguments instantiation for single-arg case.
|
||||
this.buffer_ += String(a1);
|
||||
if (opt_a2 != null) { // second argument is undefined (null == undefined)
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
this.buffer_ += arguments[i];
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the internal buffer.
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.clear = function() {
|
||||
'use strict';
|
||||
this.buffer_ = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} the length of the current contents of the buffer.
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.getLength = function() {
|
||||
'use strict';
|
||||
return this.buffer_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The concatenated string.
|
||||
* @override
|
||||
*/
|
||||
goog.string.StringBuffer.prototype.toString = function() {
|
||||
'use strict';
|
||||
return this.buffer_;
|
||||
};
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
goog.provide('goog.string.TypedString');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for strings that conform to a data type or language.
|
||||
*
|
||||
* Implementations of this interface are wrappers for strings, and typically
|
||||
* associate a type contract with the wrapped string. Concrete implementations
|
||||
* of this interface may choose to implement additional run-time type checking,
|
||||
* see for example `goog.html.SafeHtml`. If available, client code that
|
||||
* needs to ensure type membership of an object should use the type's function
|
||||
* to assert type membership, such as `goog.html.SafeHtml.unwrap`.
|
||||
* @interface
|
||||
*/
|
||||
goog.string.TypedString = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Interface marker of the TypedString interface.
|
||||
*
|
||||
* This property can be used to determine at runtime whether or not an object
|
||||
* implements this interface. All implementations of this interface set this
|
||||
* property to `true`.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.string.TypedString.prototype.implementsGoogStringTypedString;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves this wrapped string's value.
|
||||
* @return {string} The wrapped string's value.
|
||||
*/
|
||||
goog.string.TypedString.prototype.getTypedStringValue;
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Defines the collection interface.
|
||||
*/
|
||||
|
||||
goog.provide('goog.structs.Collection');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface for a collection of values.
|
||||
* @interface
|
||||
* @template T
|
||||
*/
|
||||
goog.structs.Collection = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to add to the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.add;
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to remove from the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.remove;
|
||||
|
||||
|
||||
/**
|
||||
* @param {T} value Value to find in the collection.
|
||||
* @return {boolean} Whether the collection contains the specified value.
|
||||
*/
|
||||
goog.structs.Collection.prototype.contains;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of values stored in the collection.
|
||||
*/
|
||||
goog.structs.Collection.prototype.getCount;
|
||||
Executable
+348
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Datastructure: Heap.
|
||||
*
|
||||
*
|
||||
* This file provides the implementation of a Heap datastructure. Smaller keys
|
||||
* rise to the top.
|
||||
*
|
||||
* The big-O notation for all operations are below:
|
||||
* <pre>
|
||||
* Method big-O
|
||||
* ----------------------------------------------------------------------------
|
||||
* - insert O(logn)
|
||||
* - remove O(logn)
|
||||
* - peek O(1)
|
||||
* - contains O(n)
|
||||
* </pre>
|
||||
*/
|
||||
// TODO(user): Should this rely on natural ordering via some Comparable
|
||||
// interface?
|
||||
|
||||
|
||||
goog.provide('goog.structs.Heap');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.structs.Node');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for a Heap datastructure.
|
||||
*
|
||||
* @param {goog.structs.Heap|Object=} opt_heap Optional goog.structs.Heap or
|
||||
* Object to initialize heap with.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*/
|
||||
goog.structs.Heap = function(opt_heap) {
|
||||
'use strict';
|
||||
/**
|
||||
* The nodes of the heap.
|
||||
* @private
|
||||
* @type {Array<goog.structs.Node>}
|
||||
*/
|
||||
this.nodes_ = [];
|
||||
|
||||
if (opt_heap) {
|
||||
this.insertAll(opt_heap);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Insert the given value into the heap with the given key.
|
||||
* @param {K} key The key.
|
||||
* @param {V} value The value.
|
||||
*/
|
||||
goog.structs.Heap.prototype.insert = function(key, value) {
|
||||
'use strict';
|
||||
var node = new goog.structs.Node(key, value);
|
||||
var nodes = this.nodes_;
|
||||
nodes.push(node);
|
||||
this.moveUp_(nodes.length - 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple key-value pairs from another goog.structs.Heap or Object
|
||||
* @param {goog.structs.Heap|Object} heap Object containing the data to add.
|
||||
*/
|
||||
goog.structs.Heap.prototype.insertAll = function(heap) {
|
||||
'use strict';
|
||||
var keys, values;
|
||||
if (heap instanceof goog.structs.Heap) {
|
||||
keys = heap.getKeys();
|
||||
values = heap.getValues();
|
||||
|
||||
// If it is a heap and the current heap is empty, I can rely on the fact
|
||||
// that the keys/values are in the correct order to put in the underlying
|
||||
// structure.
|
||||
if (this.getCount() <= 0) {
|
||||
var nodes = this.nodes_;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
nodes.push(new goog.structs.Node(keys[i], values[i]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
keys = goog.object.getKeys(heap);
|
||||
values = goog.object.getValues(heap);
|
||||
}
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
this.insert(keys[i], values[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves and removes the root value of this heap.
|
||||
* @return {V} The value removed from the root of the heap. Returns
|
||||
* undefined if the heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.remove = function() {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
var count = nodes.length;
|
||||
var rootNode = nodes[0];
|
||||
if (count <= 0) {
|
||||
return undefined;
|
||||
} else if (count == 1) {
|
||||
goog.array.clear(nodes);
|
||||
} else {
|
||||
nodes[0] = nodes.pop();
|
||||
this.moveDown_(0);
|
||||
}
|
||||
return rootNode.getValue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the root value of this heap.
|
||||
* @return {V} The value at the root of the heap. Returns
|
||||
* undefined if the heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.peek = function() {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
if (nodes.length == 0) {
|
||||
return undefined;
|
||||
}
|
||||
return nodes[0].getValue();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves but does not remove the key of the root node of this heap.
|
||||
* @return {K} The key at the root of the heap. Returns undefined if the
|
||||
* heap is empty.
|
||||
*/
|
||||
goog.structs.Heap.prototype.peekKey = function() {
|
||||
'use strict';
|
||||
return this.nodes_[0] && this.nodes_[0].getKey();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the node at the given index down to its proper place in the heap.
|
||||
* @param {number} index The index of the node to move down.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.moveDown_ = function(index) {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
var count = nodes.length;
|
||||
|
||||
// Save the node being moved down.
|
||||
var node = nodes[index];
|
||||
// While the current node has a child.
|
||||
while (index < (count >> 1)) {
|
||||
var leftChildIndex = this.getLeftChildIndex_(index);
|
||||
var rightChildIndex = this.getRightChildIndex_(index);
|
||||
|
||||
// Determine the index of the smaller child.
|
||||
var smallerChildIndex = rightChildIndex < count &&
|
||||
nodes[rightChildIndex].getKey() < nodes[leftChildIndex].getKey() ?
|
||||
rightChildIndex :
|
||||
leftChildIndex;
|
||||
|
||||
// If the node being moved down is smaller than its children, the node
|
||||
// has found the correct index it should be at.
|
||||
if (nodes[smallerChildIndex].getKey() > node.getKey()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If not, then take the smaller child as the current node.
|
||||
nodes[index] = nodes[smallerChildIndex];
|
||||
index = smallerChildIndex;
|
||||
}
|
||||
nodes[index] = node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the node at the given index up to its proper place in the heap.
|
||||
* @param {number} index The index of the node to move up.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.moveUp_ = function(index) {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
var node = nodes[index];
|
||||
|
||||
// While the node being moved up is not at the root.
|
||||
while (index > 0) {
|
||||
// If the parent is less than the node being moved up, move the parent down.
|
||||
var parentIndex = this.getParentIndex_(index);
|
||||
if (nodes[parentIndex].getKey() > node.getKey()) {
|
||||
nodes[index] = nodes[parentIndex];
|
||||
index = parentIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
nodes[index] = node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the left child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the left child for.
|
||||
* @return {number} The index of the left child.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getLeftChildIndex_ = function(index) {
|
||||
'use strict';
|
||||
return index * 2 + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the right child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the right child for.
|
||||
* @return {number} The index of the right child.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getRightChildIndex_ = function(index) {
|
||||
'use strict';
|
||||
return index * 2 + 2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the parent of the node at the given index.
|
||||
* @param {number} index The index of the node to get the parent for.
|
||||
* @return {number} The index of the parent.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Heap.prototype.getParentIndex_ = function(index) {
|
||||
'use strict';
|
||||
return (index - 1) >> 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the values of the heap.
|
||||
* @return {!Array<V>} The values in the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getValues = function() {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
var rv = [];
|
||||
var l = nodes.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(nodes[i].getValue());
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the keys of the heap.
|
||||
* @return {!Array<K>} The keys in the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getKeys = function() {
|
||||
'use strict';
|
||||
var nodes = this.nodes_;
|
||||
var rv = [];
|
||||
var l = nodes.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
rv.push(nodes[i].getKey());
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the heap contains the given value.
|
||||
* @param {V} val The value to check for.
|
||||
* @return {boolean} Whether the heap contains the value.
|
||||
*/
|
||||
goog.structs.Heap.prototype.containsValue = function(val) {
|
||||
'use strict';
|
||||
return goog.array.some(this.nodes_, function(node) {
|
||||
'use strict';
|
||||
return node.getValue() == val;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the heap contains the given key.
|
||||
* @param {K} key The key to check for.
|
||||
* @return {boolean} Whether the heap contains the key.
|
||||
*/
|
||||
goog.structs.Heap.prototype.containsKey = function(key) {
|
||||
'use strict';
|
||||
return goog.array.some(this.nodes_, function(node) {
|
||||
'use strict';
|
||||
return node.getKey() == key;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a heap and returns a new heap
|
||||
* @return {!goog.structs.Heap} A new goog.structs.Heap with the same key-value
|
||||
* pairs.
|
||||
*/
|
||||
goog.structs.Heap.prototype.clone = function() {
|
||||
'use strict';
|
||||
return new goog.structs.Heap(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The number of key-value pairs in the map
|
||||
* @return {number} The number of pairs.
|
||||
*/
|
||||
goog.structs.Heap.prototype.getCount = function() {
|
||||
'use strict';
|
||||
return this.nodes_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this heap contains no elements.
|
||||
* @return {boolean} Whether this heap contains no elements.
|
||||
*/
|
||||
goog.structs.Heap.prototype.isEmpty = function() {
|
||||
'use strict';
|
||||
return this.nodes_.length === 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all elements from the heap.
|
||||
*/
|
||||
goog.structs.Heap.prototype.clear = function() {
|
||||
'use strict';
|
||||
goog.array.clear(this.nodes_);
|
||||
};
|
||||
Executable
+560
@@ -0,0 +1,560 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Datastructure: Hash Map.
|
||||
*
|
||||
*
|
||||
* This file contains an implementation of a Map structure. It implements a lot
|
||||
* of the methods used in goog.structs so those functions work on hashes. This
|
||||
* is best suited for complex key types. For simple keys such as numbers and
|
||||
* strings consider using the lighter-weight utilities in goog.object.
|
||||
* @deprecated goog.structs.Map is deprecated in favour of ES6 Maps.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Map');
|
||||
|
||||
goog.require('goog.collections.iters');
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.iter.es6');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for Hash Map datastructure.
|
||||
* @param {*=} opt_map Map or Object to initialize the map with.
|
||||
* @param {...*} var_args If 2 or more arguments are present then they
|
||||
* will be used as key-value pairs.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
* @deprecated This type is misleading: use ES6 Map instead.
|
||||
*/
|
||||
goog.structs.Map = function(opt_map, var_args) {
|
||||
'use strict';
|
||||
/**
|
||||
* Underlying JS object used to implement the map.
|
||||
* @private {!Object}
|
||||
*/
|
||||
this.map_ = {};
|
||||
|
||||
/**
|
||||
* An array of keys. This is necessary for two reasons:
|
||||
* 1. Iterating the keys using for (var key in this.map_) allocates an
|
||||
* object for every key in IE which is really bad for IE6 GC perf.
|
||||
* 2. Without a side data structure, we would need to escape all the keys
|
||||
* as that would be the only way we could tell during iteration if the
|
||||
* key was an internal key or a property of the object.
|
||||
*
|
||||
* This array can contain deleted keys so it's necessary to check the map
|
||||
* as well to see if the key is still in the map (this doesn't require a
|
||||
* memory allocation in IE).
|
||||
* @private {!Array<string>}
|
||||
*/
|
||||
this.keys_ = [];
|
||||
|
||||
/**
|
||||
* The number of key value pairs in the map.
|
||||
* @const {number}
|
||||
*/
|
||||
this.size = 0;
|
||||
|
||||
/**
|
||||
* Version used to detect changes while iterating.
|
||||
* @private {number}
|
||||
*/
|
||||
this.version_ = 0;
|
||||
|
||||
var argLength = arguments.length;
|
||||
|
||||
if (argLength > 1) {
|
||||
if (argLength % 2) {
|
||||
throw new Error('Uneven number of arguments');
|
||||
}
|
||||
for (var i = 0; i < argLength; i += 2) {
|
||||
this.set(arguments[i], arguments[i + 1]);
|
||||
}
|
||||
} else if (opt_map) {
|
||||
this.addAll(/** @type {!Object} */ (opt_map));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of key-value pairs in the map.
|
||||
* @deprecated Use the `size` property instead, for alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getCount = function() {
|
||||
'use strict';
|
||||
return this.size;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the values of the map.
|
||||
* @return {!Array<V>} The values in the map.
|
||||
* @deprecated Use `Array.from(map.values())` instead, for alignment with ES6
|
||||
* Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getValues = function() {
|
||||
'use strict';
|
||||
this.cleanupKeysArray_();
|
||||
|
||||
var rv = [];
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
rv.push(this.map_[key]);
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the keys of the map.
|
||||
* @return {!Array<string>} Array of string values.
|
||||
* @deprecated Use `Array.from(map.keys())` instead, for alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getKeys = function() {
|
||||
'use strict';
|
||||
this.cleanupKeysArray_();
|
||||
return /** @type {!Array<string>} */ (this.keys_.concat());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the map contains the given key.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean} Whether the map contains the key.
|
||||
* @deprecated Use `has` instead, for alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.containsKey = function(key) {
|
||||
'use strict';
|
||||
return this.has(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the map contains the given key.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean} Whether the map contains the key.
|
||||
*/
|
||||
goog.structs.Map.prototype.has = function(key) {
|
||||
'use strict';
|
||||
return goog.structs.Map.hasKey_(this.map_, key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the map contains the given value. This is O(n).
|
||||
* @param {V} val The value to check for.
|
||||
* @return {boolean} Whether the map contains the value.
|
||||
*/
|
||||
goog.structs.Map.prototype.containsValue = function(val) {
|
||||
'use strict';
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether this map is equal to the argument map.
|
||||
* @param {goog.structs.Map} otherMap The map against which to test equality.
|
||||
* @param {function(V, V): boolean=} opt_equalityFn Optional equality function
|
||||
* to test equality of values. If not specified, this will test whether
|
||||
* the values contained in each map are identical objects.
|
||||
* @return {boolean} Whether the maps are equal.
|
||||
* @deprecated Use goog.collections.maps.equals(thisMap, otherMap,
|
||||
* opt_equalityFn) instead, for alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
|
||||
'use strict';
|
||||
if (this === otherMap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.size != otherMap.getCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
|
||||
|
||||
this.cleanupKeysArray_();
|
||||
for (var key, i = 0; key = this.keys_[i]; i++) {
|
||||
if (!equalityFn(this.get(key), otherMap.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default equality test for values.
|
||||
* @param {*} a The first value.
|
||||
* @param {*} b The second value.
|
||||
* @return {boolean} Whether a and b reference the same object.
|
||||
*/
|
||||
goog.structs.Map.defaultEquals = function(a, b) {
|
||||
'use strict';
|
||||
return a === b;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the map is empty.
|
||||
* @deprecated Use the size property and compare against 0, for alignment with
|
||||
* ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.isEmpty = function() {
|
||||
'use strict';
|
||||
return this.size == 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all key-value pairs from the map.
|
||||
*/
|
||||
goog.structs.Map.prototype.clear = function() {
|
||||
'use strict';
|
||||
this.map_ = {};
|
||||
this.keys_.length = 0;
|
||||
this.setSizeInternal_(0);
|
||||
this.version_ = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes a key-value pair based on the key. This is O(logN) amortized due to
|
||||
* updating the keys array whenever the count becomes half the size of the keys
|
||||
* in the keys array.
|
||||
* @param {*} key The key to remove.
|
||||
* @return {boolean} Whether object was removed.
|
||||
* @deprecated Use `delete` instead, for alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.remove = function(key) {
|
||||
return this.delete(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a key-value pair based on the key. This is O(logN) amortized due
|
||||
* to updating the keys array whenever the count becomes half the size of
|
||||
* the keys in the keys array.
|
||||
* @param {*} key The key to remove.
|
||||
* @return {boolean} Whether object was removed.
|
||||
*/
|
||||
goog.structs.Map.prototype.delete = function(key) {
|
||||
'use strict';
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
delete this.map_[key];
|
||||
this.setSizeInternal_(this.size - 1);
|
||||
this.version_++;
|
||||
|
||||
// clean up the keys array if the threshold is hit
|
||||
if (this.keys_.length > 2 * this.size) {
|
||||
this.cleanupKeysArray_();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the temp keys array by removing entries that are no longer in the
|
||||
* map.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Map.prototype.cleanupKeysArray_ = function() {
|
||||
'use strict';
|
||||
if (this.size != this.keys_.length) {
|
||||
// First remove keys that are no longer in the map.
|
||||
var srcIndex = 0;
|
||||
var destIndex = 0;
|
||||
while (srcIndex < this.keys_.length) {
|
||||
var key = this.keys_[srcIndex];
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
this.keys_[destIndex++] = key;
|
||||
}
|
||||
srcIndex++;
|
||||
}
|
||||
this.keys_.length = destIndex;
|
||||
}
|
||||
|
||||
if (this.size != this.keys_.length) {
|
||||
// If the count still isn't correct, that means we have duplicates. This can
|
||||
// happen when the same key is added and removed multiple times. Now we have
|
||||
// to allocate one extra Object to remove the duplicates. This could have
|
||||
// been done in the first pass, but in the common case, we can avoid
|
||||
// allocating an extra object by only doing this when necessary.
|
||||
var seen = {};
|
||||
var srcIndex = 0;
|
||||
var destIndex = 0;
|
||||
while (srcIndex < this.keys_.length) {
|
||||
var key = this.keys_[srcIndex];
|
||||
if (!(goog.structs.Map.hasKey_(seen, key))) {
|
||||
this.keys_[destIndex++] = key;
|
||||
seen[key] = 1;
|
||||
}
|
||||
srcIndex++;
|
||||
}
|
||||
this.keys_.length = destIndex;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value for the given key. If the key is not found and the default
|
||||
* value is not given this will return `undefined`.
|
||||
* @param {*} key The key to get the value for.
|
||||
* @param {DEFAULT=} opt_val The value to return if no item is found for the
|
||||
* given key, defaults to undefined.
|
||||
* @return {V|DEFAULT} The value for the given key.
|
||||
* @template DEFAULT
|
||||
*/
|
||||
goog.structs.Map.prototype.get = function(key, opt_val) {
|
||||
'use strict';
|
||||
if (goog.structs.Map.hasKey_(this.map_, key)) {
|
||||
return this.map_[key];
|
||||
}
|
||||
return opt_val;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a key-value pair to the map.
|
||||
* @param {*} key The key.
|
||||
* @param {V} value The value to add.
|
||||
*/
|
||||
goog.structs.Map.prototype.set = function(key, value) {
|
||||
'use strict';
|
||||
if (!(goog.structs.Map.hasKey_(this.map_, key))) {
|
||||
this.setSizeInternal_(this.size + 1);
|
||||
// TODO(johnlenz): This class lies, it claims to return an array of string
|
||||
// keys, but instead returns the original object used.
|
||||
this.keys_.push(/** @type {?} */ (key));
|
||||
// Only change the version if we add a new key.
|
||||
this.version_++;
|
||||
}
|
||||
this.map_[key] = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds multiple key-value pairs from another goog.structs.Map or Object.
|
||||
* @param {?Object} map Object containing the data to add.
|
||||
* @deprecated Use goog.collections.maps.setAll(thisMap, map.entries()) if map
|
||||
* is an ES6 or goog.structs Map, or
|
||||
* goog.collections.maps.setAll(thisMap, Object.entries(map)) otherwise.
|
||||
*/
|
||||
goog.structs.Map.prototype.addAll = function(map) {
|
||||
'use strict';
|
||||
if (map instanceof goog.structs.Map) {
|
||||
var keys = map.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
this.set(keys[i], map.get(keys[i]));
|
||||
}
|
||||
} else {
|
||||
for (var key in map) {
|
||||
this.set(key, map[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the given function on each entry in the map.
|
||||
* @param {function(this:T, V, K, goog.structs.Map<K,V>)} f
|
||||
* @param {T=} opt_obj The value of "this" inside f.
|
||||
* @template T
|
||||
* @deprecated Use ES6 Iteration instead.
|
||||
*/
|
||||
goog.structs.Map.prototype.forEach = function(f, opt_obj) {
|
||||
'use strict';
|
||||
var keys = this.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var value = this.get(key);
|
||||
f.call(opt_obj, value, key, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a map and returns a new map.
|
||||
* @return {!goog.structs.Map} A new map with the same key-value pairs.
|
||||
* @deprecated Use `new Map(thisMap.entries())` instead, for alignment with
|
||||
* ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.clone = function() {
|
||||
'use strict';
|
||||
return new goog.structs.Map(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new map in which all the keys and values are interchanged
|
||||
* (keys become values and values become keys). If multiple keys map to the
|
||||
* same value, the chosen transposed value is implementation-dependent.
|
||||
*
|
||||
* It acts very similarly to {goog.object.transpose(Object)}.
|
||||
*
|
||||
* @return {!goog.structs.Map} The transposed map.
|
||||
* @deprecated Use goog.collections.maps.transpose instead, for alignment with
|
||||
* ES6 Maps.
|
||||
*/
|
||||
goog.structs.Map.prototype.transpose = function() {
|
||||
'use strict';
|
||||
var transposed = new goog.structs.Map();
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
var value = this.map_[key];
|
||||
transposed.set(value, key);
|
||||
}
|
||||
|
||||
return transposed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!Object} Object representation of the map.
|
||||
* @deprecated Use goog.collections.maps.toObject(thisMap) instead, for aligment
|
||||
* with ES6 Maps.
|
||||
*/
|
||||
goog.structs.Map.prototype.toObject = function() {
|
||||
'use strict';
|
||||
this.cleanupKeysArray_();
|
||||
var obj = {};
|
||||
for (var i = 0; i < this.keys_.length; i++) {
|
||||
var key = this.keys_[i];
|
||||
obj[key] = this.map_[key];
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the keys in the map. Removal of keys
|
||||
* while iterating might have undesired side effects.
|
||||
* @return {!goog.iter.Iterator} An iterator over the keys in the map.
|
||||
* @deprecated Use `keys()` with native iteration protocols, for alignment
|
||||
* with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getKeyIterator = function() {
|
||||
'use strict';
|
||||
return this.__iterator__(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!IteratorIterable<K>} An ES6 Iterator that iterates over the maps
|
||||
* keys.
|
||||
*/
|
||||
goog.structs.Map.prototype.keys = function() {
|
||||
'use strict';
|
||||
return goog.iter.es6.ShimIterable.of(this.getKeyIterator()).toEs6();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the values in the map. Removal of
|
||||
* keys while iterating might have undesired side effects.
|
||||
* @return {!goog.iter.Iterator} An iterator over the values in the map.
|
||||
* @deprecated Use `values()` with native iteration protocols, for alignment
|
||||
* with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.getValueIterator = function() {
|
||||
'use strict';
|
||||
return this.__iterator__(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!IteratorIterable<V>} An ES6 Iterator that iterates over the maps
|
||||
* values.
|
||||
*/
|
||||
goog.structs.Map.prototype.values = function() {
|
||||
'use strict';
|
||||
return goog.iter.es6.ShimIterable.of(this.getValueIterator()).toEs6();
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!IteratorIterable<!Array<K|V>>} An iterator of entries in this map.
|
||||
* The type is actually Array<[K,V]> but this is not representable in the
|
||||
* Closure Type System.
|
||||
*/
|
||||
goog.structs.Map.prototype.entries = function() {
|
||||
const self = this;
|
||||
return goog.collections.iters.map(this.keys(), function(key) {
|
||||
return [key, self.get(key)];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an iterator that iterates over the values or the keys in the map.
|
||||
* This throws an exception if the map was mutated since the iterator was
|
||||
* created.
|
||||
* @param {boolean=} opt_keys True to iterate over the keys. False to iterate
|
||||
* over the values. The default value is false.
|
||||
* @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
|
||||
* @deprecated Call either `keys` or `values` and use native iteration, for
|
||||
* alignment with ES6 Map.
|
||||
*/
|
||||
goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
|
||||
'use strict';
|
||||
// Clean up keys to minimize the risk of iterating over dead keys.
|
||||
this.cleanupKeysArray_();
|
||||
|
||||
var i = 0;
|
||||
var version = this.version_;
|
||||
var selfObj = this;
|
||||
|
||||
var newIter = new goog.iter.Iterator;
|
||||
newIter.nextValueOrThrow = function() {
|
||||
'use strict';
|
||||
if (version != selfObj.version_) {
|
||||
throw new Error('The map has changed since the iterator was created');
|
||||
}
|
||||
if (i >= selfObj.keys_.length) {
|
||||
throw goog.iter.StopIteration;
|
||||
}
|
||||
var key = selfObj.keys_[i++];
|
||||
return opt_keys ? key : selfObj.map_[key];
|
||||
};
|
||||
|
||||
return newIter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Assigns to the size property to isolate supressions of const assignment to
|
||||
* only where they are needed.
|
||||
* @param {number} newSize The size to update to.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Map.prototype.setSizeInternal_ = function(newSize) {
|
||||
/** @suppress {const} */
|
||||
this.size = newSize;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safe way to test for hasOwnProperty. It even allows testing for
|
||||
* 'hasOwnProperty'.
|
||||
* @param {!Object} obj The object to test for presence of the given key.
|
||||
* @param {*} key The key to check for.
|
||||
* @return {boolean} Whether the object has the key.
|
||||
* @private
|
||||
*/
|
||||
goog.structs.Map.hasKey_ = function(obj, key) {
|
||||
'use strict';
|
||||
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||
};
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright The Closure Library Authors.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Generic immutable node object to be used in collections.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.structs.Node');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A generic immutable node. This can be used in various collections that
|
||||
* require a node object for its item (such as a heap).
|
||||
* @param {K} key Key.
|
||||
* @param {V} value Value.
|
||||
* @constructor
|
||||
* @template K, V
|
||||
*/
|
||||
goog.structs.Node = function(key, value) {
|
||||
'use strict';
|
||||
/**
|
||||
* The key.
|
||||
* @private {K}
|
||||
*/
|
||||
this.key_ = key;
|
||||
|
||||
/**
|
||||
* The value.
|
||||
* @private {V}
|
||||
*/
|
||||
this.value_ = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the key.
|
||||
* @return {K} The key.
|
||||
*/
|
||||
goog.structs.Node.prototype.getKey = function() {
|
||||
'use strict';
|
||||
return this.key_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value.
|
||||
* @return {V} The value.
|
||||
*/
|
||||
goog.structs.Node.prototype.getValue = function() {
|
||||
'use strict';
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones a node and returns a new node.
|
||||
* @return {!goog.structs.Node<K, V>} A new goog.structs.Node with the same
|
||||
* key value pair.
|
||||
*/
|
||||
goog.structs.Node.prototype.clone = function() {
|
||||
'use strict';
|
||||
return new goog.structs.Node(this.key_, this.value_);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user