initial commit

This commit is contained in:
2026-06-25 21:30:32 +00:00
commit 328faf6251
220 changed files with 162103 additions and 0 deletions
+1793
View File
File diff suppressed because it is too large Load Diff
+471
View File
@@ -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;
}
};
+77
View File
@@ -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_;
}
};
+237
View File
@@ -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;
});
+149
View File
@@ -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;
};
+28
View File
@@ -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;
+123
View File
@@ -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;
+3889
View File
File diff suppressed because it is too large Load Diff
+222
View File
@@ -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));
};
+159
View File
@@ -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;
+747
View File
@@ -0,0 +1,747 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Logging and debugging utilities.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug');
goog.require('goog.array');
goog.require('goog.debug.errorcontext');
/** @define {boolean} Whether logging should be enabled. */
goog.debug.LOGGING_ENABLED =
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
/** @define {boolean} Whether to force "sloppy" stack building. */
goog.debug.FORCE_SLOPPY_STACKS =
goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);
/**
* @define {boolean} TODO(user): Remove this hack once bug is resolved.
*/
goog.debug.CHECK_FOR_THROWN_EVENT =
goog.define('goog.debug.CHECK_FOR_THROWN_EVENT', false);
/**
* Catches onerror events fired by windows and similar objects.
* @param {function(Object)} logFunc The function to call with the error
* information.
* @param {boolean=} opt_cancel Whether to stop the error from reaching the
* browser.
* @param {Object=} opt_target Object that fires onerror events.
* @suppress {strictMissingProperties} onerror is not defined as a property
* on Object.
*/
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
'use strict';
var target = opt_target || goog.global;
var oldErrorHandler = target.onerror;
var retVal = !!opt_cancel;
/**
* New onerror handler for this target. This onerror handler follows the spec
* according to
* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
* The spec was changed in August 2013 to support receiving column information
* and an error object for all scripts on the same origin or cross origin
* scripts with the proper headers. See
* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
*
* @param {string} message The error message. For cross-origin errors, this
* will be scrubbed to just "Script error.". For new browsers that have
* updated to follow the latest spec, errors that come from origins that
* have proper cross origin headers will not be scrubbed.
* @param {string} url The URL of the script that caused the error. The URL
* will be scrubbed to "" for cross origin scripts unless the script has
* proper cross origin headers and the browser has updated to the latest
* spec.
* @param {number} line The line number in the script that the error
* occurred on.
* @param {number=} opt_col The optional column number that the error
* occurred on. Only browsers that have updated to the latest spec will
* include this.
* @param {Error=} opt_error The optional actual error object for this
* error that should include the stack. Only browsers that have updated
* to the latest spec will inlude this parameter.
* @return {boolean} Whether to prevent the error from reaching the browser.
*/
target.onerror = function(message, url, line, opt_col, opt_error) {
'use strict';
if (oldErrorHandler) {
oldErrorHandler(message, url, line, opt_col, opt_error);
}
logFunc({
message: message,
fileName: url,
line: line,
lineNumber: line,
col: opt_col,
error: opt_error
});
return retVal;
};
};
/**
* Creates a string representing an object and all its properties.
* @param {Object|null|undefined} obj Object to expose.
* @param {boolean=} opt_showFn Show the functions as well as the properties,
* default is false.
* @return {string} The string representation of `obj`.
*/
goog.debug.expose = function(obj, opt_showFn) {
'use strict';
if (typeof obj == 'undefined') {
return 'undefined';
}
if (obj == null) {
return 'NULL';
}
var str = [];
for (var x in obj) {
if (!opt_showFn && typeof obj[x] === 'function') {
continue;
}
var s = x + ' = ';
try {
s += obj[x];
} catch (e) {
s += '*** ' + e + ' ***';
}
str.push(s);
}
return str.join('\n');
};
/**
* Creates a string representing a given primitive or object, and for an
* object, all its properties and nested objects. NOTE: The output will include
* Uids on all objects that were exposed. Any added Uids will be removed before
* returning.
* @param {*} obj Object to expose.
* @param {boolean=} opt_showFn Also show properties that are functions (by
* default, functions are omitted).
* @return {string} A string representation of `obj`.
*/
goog.debug.deepExpose = function(obj, opt_showFn) {
'use strict';
var str = [];
// Track any objects where deepExpose added a Uid, so they can be cleaned up
// before return. We do this globally, rather than only on ancestors so that
// if the same object appears in the output, you can see it.
var uidsToCleanup = [];
var ancestorUids = {};
var helper = function(obj, space) {
'use strict';
var nestspace = space + ' ';
var indentMultiline = function(str) {
'use strict';
return str.replace(/\n/g, '\n' + space);
};
try {
if (obj === undefined) {
str.push('undefined');
} else if (obj === null) {
str.push('NULL');
} else if (typeof obj === 'string') {
str.push('"' + indentMultiline(obj) + '"');
} else if (typeof obj === 'function') {
str.push(indentMultiline(String(obj)));
} else if (goog.isObject(obj)) {
// Add a Uid if needed. The struct calls implicitly adds them.
if (!goog.hasUid(obj)) {
uidsToCleanup.push(obj);
}
var uid = goog.getUid(obj);
if (ancestorUids[uid]) {
str.push('*** reference loop detected (id=' + uid + ') ***');
} else {
ancestorUids[uid] = true;
str.push('{');
for (var x in obj) {
if (!opt_showFn && typeof obj[x] === 'function') {
continue;
}
str.push('\n');
str.push(nestspace);
str.push(x + ' = ');
helper(obj[x], nestspace);
}
str.push('\n' + space + '}');
delete ancestorUids[uid];
}
} else {
str.push(obj);
}
} catch (e) {
str.push('*** ' + e + ' ***');
}
};
helper(obj, '');
// Cleanup any Uids that were added by the deepExpose.
for (var i = 0; i < uidsToCleanup.length; i++) {
goog.removeUid(uidsToCleanup[i]);
}
return str.join('');
};
/**
* Recursively outputs a nested array as a string.
* @param {Array<?>} arr The array.
* @return {string} String representing nested array.
*/
goog.debug.exposeArray = function(arr) {
'use strict';
var str = [];
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
str.push(goog.debug.exposeArray(arr[i]));
} else {
str.push(arr[i]);
}
}
return '[ ' + str.join(', ') + ' ]';
};
/**
* Normalizes the error/exception object between browsers.
* @param {*} err Raw error object.
* @return {{
* message: (?|undefined),
* name: (?|undefined),
* lineNumber: (?|undefined),
* fileName: (?|undefined),
* stack: (?|undefined)
* }} Representation of err as an Object. It will never return err.
* @suppress {strictMissingProperties} properties not defined on err
*/
goog.debug.normalizeErrorObject = function(err) {
'use strict';
var href = goog.getObjectByName('window.location.href');
if (err == null) {
err = 'Unknown Error of type "null/undefined"';
}
if (typeof err === 'string') {
return {
'message': err,
'name': 'Unknown error',
'lineNumber': 'Not available',
'fileName': href,
'stack': 'Not available'
};
}
var lineNumber, fileName;
var threwError = false;
try {
lineNumber = err.lineNumber || err.line || 'Not available';
} catch (e) {
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
// Message: Permission denied to get property UnnamedClass.lineNumber
lineNumber = 'Not available';
threwError = true;
}
try {
fileName = err.fileName || err.filename || err.sourceURL ||
// $googDebugFname may be set before a call to eval to set the filename
// that the eval is supposed to present.
goog.global['$googDebugFname'] || href;
} catch (e) {
// Firefox 2 may also throw an error when accessing 'filename'.
fileName = 'Not available';
threwError = true;
}
var stack = goog.debug.serializeErrorStack_(err);
// The IE Error object contains only the name and the message.
// The Safari Error object uses the line and sourceURL fields.
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
!err.message || !err.name) {
var message = err.message;
if (message == null) {
if (err.constructor && err.constructor instanceof Function) {
var ctorName = err.constructor.name ?
err.constructor.name :
goog.debug.getFunctionName(err.constructor);
message = 'Unknown Error of type "' + ctorName + '"';
// TODO(user): Remove this hack once bug is resolved.
if (goog.debug.CHECK_FOR_THROWN_EVENT && ctorName == 'Event') {
try {
message = message + ' with Event.type "' + (err.type || '') + '"';
} catch (e) {
// Just give up on getting more information out of the error object.
}
}
} else {
message = 'Unknown Error of unknown type';
}
// Avoid TypeError since toString could be missing from the instance
// (e.g. if created Object.create(null)).
if (typeof err.toString === 'function' &&
Object.prototype.toString !== err.toString) {
message += ': ' + err.toString();
}
}
return {
'message': message,
'name': err.name || 'UnknownError',
'lineNumber': lineNumber,
'fileName': fileName,
'stack': stack || 'Not available'
};
}
// Standards error object
// Typed !Object. Should be a subtype of the return type, but it's not.
err.stack = stack;
// Return non-standard error to allow for consistent result (eg. enumerable).
return {
'message': err.message,
'name': err.name,
'lineNumber': err.lineNumber,
'fileName': err.fileName,
'stack': err.stack
};
};
/**
* Serialize stack by including the cause chain of the exception if it exists.
*
*
* @param {*} e an exception that may have a cause
* @param {!Object=} seen set of cause that have already been serialized
* @return {string}
* @private
* @suppress {missingProperties} properties not defined on cause and e
*/
goog.debug.serializeErrorStack_ = function(e, seen) {
'use strict';
if (!seen) {
seen = {};
}
seen[goog.debug.serializeErrorAsKey_(e)] = true;
var stack = e['stack'] || '';
// Add cause if exists.
var cause = e.cause;
if (cause && !seen[goog.debug.serializeErrorAsKey_(cause)]) {
stack += '\nCaused by: ';
// Some browsers like Chrome add the error message as the first frame of the
// stack, In this case we don't need to add it. Note: we don't use
// String.startsWith method because it might have to be polyfilled.
if (!cause.stack || cause.stack.indexOf(cause.toString()) != 0) {
stack += (typeof cause === 'string') ? cause : cause.message + '\n';
}
stack += goog.debug.serializeErrorStack_(cause, seen);
}
return stack;
};
/**
* Serialize an error to a string key.
* @param {*} e an exception
* @return {string}
* @private
*/
goog.debug.serializeErrorAsKey_ = function(e) {
'use strict';
var keyPrefix = '';
if (typeof e.toString === 'function') {
keyPrefix = '' + e;
}
return keyPrefix + e['stack'];
};
/**
* Converts an object to an Error using the object's toString if it's not
* already an Error, adds a stacktrace if there isn't one, and optionally adds
* an extra message.
* @param {*} err The original thrown error, object, or string.
* @param {string=} opt_message optional additional message to add to the
* error.
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
* it is converted to an Error which is enhanced and returned.
*/
goog.debug.enhanceError = function(err, opt_message) {
'use strict';
var error;
if (!(err instanceof Error)) {
error = Error(err);
if (Error.captureStackTrace) {
// Trim this function off the call stack, if we can.
Error.captureStackTrace(error, goog.debug.enhanceError);
}
} else {
error = err;
}
if (!error.stack) {
error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
}
if (opt_message) {
// find the first unoccupied 'messageX' property
var x = 0;
while (error['message' + x]) {
++x;
}
error['message' + x] = String(opt_message);
}
return error;
};
/**
* Converts an object to an Error using the object's toString if it's not
* already an Error, adds a stacktrace if there isn't one, and optionally adds
* context to the Error, which is reported by the closure error reporter.
* @param {*} err The original thrown error, object, or string.
* @param {!Object<string, string>=} opt_context Key-value context to add to the
* Error.
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
* it is converted to an Error which is enhanced and returned.
*/
goog.debug.enhanceErrorWithContext = function(err, opt_context) {
'use strict';
var error = goog.debug.enhanceError(err);
if (opt_context) {
for (var key in opt_context) {
goog.debug.errorcontext.addErrorContext(error, key, opt_context[key]);
}
}
return error;
};
/**
* Gets the current stack trace. Simple and iterative - doesn't worry about
* catching circular references or getting the args.
* @param {number=} opt_depth Optional maximum depth to trace back to.
* @return {string} A string with the function names of all functions in the
* stack, separated by \n.
* @suppress {es5Strict}
*/
goog.debug.getStacktraceSimple = function(opt_depth) {
'use strict';
if (!goog.debug.FORCE_SLOPPY_STACKS) {
var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
if (stack) {
return stack;
}
// NOTE: browsers that have strict mode support also have native "stack"
// properties. Fall-through for legacy browser support.
}
var sb = [];
var fn = arguments.callee.caller;
var depth = 0;
while (fn && (!opt_depth || depth < opt_depth)) {
sb.push(goog.debug.getFunctionName(fn));
sb.push('()\n');
try {
fn = fn.caller;
} catch (e) {
sb.push('[exception trying to get caller]\n');
break;
}
depth++;
if (depth >= goog.debug.MAX_STACK_DEPTH) {
sb.push('[...long stack...]');
break;
}
}
if (opt_depth && depth >= opt_depth) {
sb.push('[...reached max depth limit...]');
} else {
sb.push('[end]');
}
return sb.join('');
};
/**
* Max length of stack to try and output
* @type {number}
*/
goog.debug.MAX_STACK_DEPTH = 50;
/**
* @param {Function} fn The function to start getting the trace from.
* @return {?string}
* @private
*/
goog.debug.getNativeStackTrace_ = function(fn) {
'use strict';
var tempErr = new Error();
if (Error.captureStackTrace) {
Error.captureStackTrace(tempErr, fn);
return String(tempErr.stack);
} else {
// IE10, only adds stack traces when an exception is thrown.
try {
throw tempErr;
} catch (e) {
tempErr = e;
}
var stack = tempErr.stack;
if (stack) {
return String(stack);
}
}
return null;
};
/**
* Gets the current stack trace, either starting from the caller or starting
* from a specified function that's currently on the call stack.
* @param {?Function=} fn If provided, when collecting the stack trace all
* frames above the topmost call to this function, including that call,
* will be left out of the stack trace.
* @return {string} Stack trace.
* @suppress {es5Strict}
*/
goog.debug.getStacktrace = function(fn) {
'use strict';
var stack;
if (!goog.debug.FORCE_SLOPPY_STACKS) {
// Try to get the stack trace from the environment if it is available.
var contextFn = fn || goog.debug.getStacktrace;
stack = goog.debug.getNativeStackTrace_(contextFn);
}
if (!stack) {
// NOTE: browsers that have strict mode support also have native "stack"
// properties. This function will throw in strict mode.
stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);
}
return stack;
};
/**
* Private helper for getStacktrace().
* @param {?Function} fn If provided, when collecting the stack trace all
* frames above the topmost call to this function, including that call,
* will be left out of the stack trace.
* @param {Array<!Function>} visited List of functions visited so far.
* @return {string} Stack trace starting from function fn.
* @suppress {es5Strict}
* @private
*/
goog.debug.getStacktraceHelper_ = function(fn, visited) {
'use strict';
var sb = [];
// Circular reference, certain functions like bind seem to cause a recursive
// loop so we need to catch circular references
if (goog.array.contains(visited, fn)) {
sb.push('[...circular reference...]');
// Traverse the call stack until function not found or max depth is reached
} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
sb.push(goog.debug.getFunctionName(fn) + '(');
var args = fn.arguments;
// Args may be null for some special functions such as host objects or eval.
for (var i = 0; args && i < args.length; i++) {
if (i > 0) {
sb.push(', ');
}
var argDesc;
var arg = args[i];
switch (typeof arg) {
case 'object':
argDesc = arg ? 'object' : 'null';
break;
case 'string':
argDesc = arg;
break;
case 'number':
argDesc = String(arg);
break;
case 'boolean':
argDesc = arg ? 'true' : 'false';
break;
case 'function':
argDesc = goog.debug.getFunctionName(arg);
argDesc = argDesc ? argDesc : '[fn]';
break;
case 'undefined':
default:
argDesc = typeof arg;
break;
}
if (argDesc.length > 40) {
argDesc = argDesc.substr(0, 40) + '...';
}
sb.push(argDesc);
}
visited.push(fn);
sb.push(')\n');
try {
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
} catch (e) {
sb.push('[exception trying to get caller]\n');
}
} else if (fn) {
sb.push('[...long stack...]');
} else {
sb.push('[end]');
}
return sb.join('');
};
/**
* Gets a function name
* @param {Function} fn Function to get name of.
* @return {string} Function's name.
*/
goog.debug.getFunctionName = function(fn) {
'use strict';
if (goog.debug.fnNameCache_[fn]) {
return goog.debug.fnNameCache_[fn];
}
// Heuristically determine function name based on code.
var functionSource = String(fn);
if (!goog.debug.fnNameCache_[functionSource]) {
var matches = /function\s+([^\(]+)/m.exec(functionSource);
if (matches) {
var method = matches[1];
goog.debug.fnNameCache_[functionSource] = method;
} else {
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
}
}
return goog.debug.fnNameCache_[functionSource];
};
/**
* Makes whitespace visible by replacing it with printable characters.
* This is useful in finding diffrences between the expected and the actual
* output strings of a testcase.
* @param {string} string whose whitespace needs to be made visible.
* @return {string} string whose whitespace is made visible.
*/
goog.debug.makeWhitespaceVisible = function(string) {
'use strict';
return string.replace(/ /g, '[_]')
.replace(/\f/g, '[f]')
.replace(/\n/g, '[n]\n')
.replace(/\r/g, '[r]')
.replace(/\t/g, '[t]');
};
/**
* Returns the type of a value. If a constructor is passed, and a suitable
* string cannot be found, 'unknown type name' will be returned.
*
* <p>Forked rather than moved from {@link goog.asserts.getType_}
* to avoid adding a dependency to goog.asserts.
* @param {*} value A constructor, object, or primitive.
* @return {string} The best display name for the value, or 'unknown type name'.
*/
goog.debug.runtimeType = function(value) {
'use strict';
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return /** @type {string} */ (value.constructor.displayName) ||
value.constructor.name || Object.prototype.toString.call(value);
} else {
return value === null ? 'null' : typeof value;
}
};
/**
* Hash map for storing function names that have already been looked up.
* @type {Object}
* @private
*/
goog.debug.fnNameCache_ = {};
/**
* Private internal function to support goog.debug.freeze.
* @param {T} arg
* @return {T}
* @template T
* @private
*/
goog.debug.freezeInternal_ = goog.DEBUG && Object.freeze || function(arg) {
'use strict';
return arg;
};
/**
* Freezes the given object, but only in debug mode (and in browsers that
* support it). Note that this is a shallow freeze, so for deeply nested
* objects it must be called at every level to ensure deep immutability.
* @param {T} arg
* @return {T}
* @template T
*/
goog.debug.freeze = function(arg) {
'use strict';
// NOTE: this compiles to nothing, but hides the possible side effect of
// freezeInternal_ from the compiler so that the entire call can be
// removed if the result is not used.
return {
valueOf: function() {
'use strict';
return goog.debug.freezeInternal_(arg);
}
}.valueOf();
};
+158
View File
@@ -0,0 +1,158 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview A global registry for entry points into a program,
* so that they can be instrumented. Each module should register their
* entry points with this registry. Designed to be compiled out
* if no instrumentation is requested.
*
* Entry points may be registered before or after a call to
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
* later, the existing monitor will instrument the new entry point.
*/
goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');
goog.require('goog.asserts');
/**
* @interface
*/
goog.debug.entryPointRegistry.EntryPointMonitor = function() {};
/**
* Instruments a function.
*
* @param {!Function} fn A function to instrument.
* @return {!Function} The instrumented function.
*/
goog.debug.entryPointRegistry.EntryPointMonitor.prototype.wrap;
/**
* Try to remove an instrumentation wrapper created by this monitor.
* If the function passed to unwrap is not a wrapper created by this
* monitor, then we will do nothing.
*
* Notice that some wrappers may not be unwrappable. For example, if other
* monitors have applied their own wrappers, then it will be impossible to
* unwrap them because their wrappers will have captured our wrapper.
*
* So it is important that entry points are unwrapped in the reverse
* order that they were wrapped.
*
* @param {!Function} fn A function to unwrap.
* @return {!Function} The unwrapped function, or `fn` if it was not
* a wrapped function created by this monitor.
*/
goog.debug.entryPointRegistry.EntryPointMonitor.prototype.unwrap;
/**
* Alias for goog.debug.entryPointRegistry.EntryPointMonitor, for compatibility
* purposes.
* @const
*/
goog.debug.EntryPointMonitor = goog.debug.entryPointRegistry.EntryPointMonitor;
/**
* An array of entry point callbacks.
* @type {!Array<function(!Function)>}
* @private
*/
goog.debug.entryPointRegistry.refList_ = [];
/**
* Monitors that should wrap all the entry points.
* @type {!Array<!goog.debug.EntryPointMonitor>}
* @private
*/
goog.debug.entryPointRegistry.monitors_ = [];
/**
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
* Checking this allows the compiler to optimize out the registrations.
* @type {boolean}
* @private
*/
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
/**
* Register an entry point with this module.
*
* The entry point will be instrumented when a monitor is passed to
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
* entry point is instrumented immediately.
*
* @param {function(!Function)} callback A callback function which is called
* with a transforming function to instrument the entry point. The callback
* is responsible for wrapping the relevant entry point with the
* transforming function.
*/
goog.debug.entryPointRegistry.register = function(callback) {
'use strict';
// Don't use push(), so that this can be compiled out.
goog.debug.entryPointRegistry
.refList_[goog.debug.entryPointRegistry.refList_.length] = callback;
// If no one calls monitorAll, this can be compiled out.
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
var monitors = goog.debug.entryPointRegistry.monitors_;
for (var i = 0; i < monitors.length; i++) {
callback(goog.bind(monitors[i].wrap, monitors[i]));
}
}
};
/**
* Configures a monitor to wrap all entry points.
*
* Entry points that have already been registered are immediately wrapped by
* the monitor. When an entry point is registered in the future, it will also
* be wrapped by the monitor when it is registered.
*
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
*/
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
'use strict';
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
var transformer = goog.bind(monitor.wrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
goog.debug.entryPointRegistry.monitors_.push(monitor);
};
/**
* Try to unmonitor all the entry points that have already been registered. If
* an entry point is registered in the future, it will not be wrapped by the
* monitor when it is registered. Note that this may fail if the entry points
* have additional wrapping.
*
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
* the entry points.
* @throws {Error} If the monitor is not the most recently configured monitor.
*/
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
'use strict';
var monitors = goog.debug.entryPointRegistry.monitors_;
goog.asserts.assert(
monitor == monitors[monitors.length - 1],
'Only the most recent monitor can be unwrapped.');
var transformer = goog.bind(monitor.unwrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
monitors.length--;
};
+72
View File
@@ -0,0 +1,72 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Provides a base class for custom Error objects such that the
* stack is correctly maintained.
*
* You should never need to throw DebugError(msg) directly, Error(msg) is
* sufficient.
*/
goog.module('goog.debug.Error');
goog.module.declareLegacyNamespace();
/**
* Base class for custom error objects.
* @param {*=} msg The message associated with the error.
* @param {{
* message: (?|undefined),
* name: (?|undefined),
* lineNumber: (?|undefined),
* fileName: (?|undefined),
* stack: (?|undefined),
* cause: (?|undefined),
* }=} cause The original error object to chain with.
* @constructor
* @extends {Error}
*/
function DebugError(msg = undefined, cause = undefined) {
// Attempt to ensure there is a stack trace.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DebugError);
} else {
const stack = new Error().stack;
if (stack) {
/** @override */
this.stack = stack;
}
}
if (msg) {
/** @override */
this.message = String(msg);
}
if (cause !== undefined) {
/** @type {?} */
this.cause = cause;
}
/**
* Whether to report this error to the server. Setting this to false will
* cause the error reporter to not report the error back to the server,
* which can be useful if the client knows that the error has already been
* logged on the server.
* @type {boolean}
*/
this.reportErrorToServer = true;
}
goog.inherits(DebugError, Error);
/** @override @type {string} */
DebugError.prototype.name = 'CustomError';
exports = DebugError;
+43
View File
@@ -0,0 +1,43 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Provides methods dealing with context on error objects.
*/
goog.provide('goog.debug.errorcontext');
/**
* Adds key-value context to the error.
* @param {!Error} err The error to add context to.
* @param {string} contextKey Key for the context to be added.
* @param {string} contextValue Value for the context to be added.
*/
goog.debug.errorcontext.addErrorContext = function(
err, contextKey, contextValue) {
'use strict';
if (!err[goog.debug.errorcontext.CONTEXT_KEY_]) {
err[goog.debug.errorcontext.CONTEXT_KEY_] = {};
}
err[goog.debug.errorcontext.CONTEXT_KEY_][contextKey] = contextValue;
};
/**
* @param {!Error} err The error to get context from.
* @return {!Object<string, string>} The context of the provided error.
*/
goog.debug.errorcontext.getErrorContext = function(err) {
'use strict';
return err[goog.debug.errorcontext.CONTEXT_KEY_] || {};
};
// TODO(user): convert this to a Symbol once goog.debug.ErrorReporter is
// able to use ES6.
/** @private @const {string} */
goog.debug.errorcontext.CONTEXT_KEY_ = '__closure__error__context__984382';
+364
View File
@@ -0,0 +1,364 @@
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Error handling utilities.
*/
goog.provide('goog.debug.ErrorHandler');
goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');
goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('goog.debug.EntryPointMonitor');
goog.require('goog.debug.Error');
/**
* The ErrorHandler can be used to to wrap functions with a try/catch
* statement. If an exception is thrown, the given error handler function will
* be called.
*
* When this object is disposed, it will stop handling exceptions and tracing.
* It will also try to restore window.setTimeout and window.setInterval
* if it wrapped them. Notice that in the general case, it is not technically
* possible to remove the wrapper, because functions have no knowledge of
* what they have been assigned to. So the app is responsible for other
* forms of unwrapping.
*
* @param {Function} handler Handler for exceptions.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.debug.EntryPointMonitor}
*/
goog.debug.ErrorHandler = function(handler) {
'use strict';
goog.debug.ErrorHandler.base(this, 'constructor');
/**
* Handler for exceptions, which can do logging, reporting, etc.
* @type {Function}
* @private
*/
this.errorHandlerFn_ = handler;
/**
* Whether errors should be wrapped in
* goog.debug.ErrorHandler.ProtectedFunctionError before rethrowing.
* @type {boolean}
* @private
*/
this.wrapErrors_ = true; // TODO(malteubl) Change default.
/**
* Whether to add a prefix to all error messages. The prefix is
* goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX. This option
* only has an effect if this.wrapErrors_ is set to false.
* @type {boolean}
* @private
*/
this.prefixErrorMessages_ = false;
};
goog.inherits(goog.debug.ErrorHandler, goog.Disposable);
/** @override */
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
'use strict';
return this.protectEntryPoint(goog.asserts.assertFunction(fn));
};
/** @override */
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
'use strict';
goog.asserts.assertFunction(fn);
return fn[this.getFunctionIndex_(false)] || fn;
};
/**
* Get the index for a function. Used for internal indexing.
* @param {boolean} wrapper True for the wrapper; false for the wrapped.
* @return {string} The index where we should store the function in its
* wrapper/wrapped function.
* @private
*/
goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
'use strict';
return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
};
/**
* Installs exception protection for an entry point function. When an exception
* is thrown from a protected function, a handler will be invoked to handle it.
*
* @param {Function} fn An entry point function to be protected.
* @return {!Function} A protected wrapper function that calls the entry point
* function.
*/
goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
'use strict';
var protectedFnName = this.getFunctionIndex_(true);
if (!fn[protectedFnName]) {
var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
wrapper[this.getFunctionIndex_(false)] = fn;
}
return fn[protectedFnName];
};
/**
* Helps {@link #protectEntryPoint} by actually creating the protected
* wrapper function, after {@link #protectEntryPoint} determines that one does
* not already exist for the given function. Can be overridden by subclasses
* that may want to implement different error handling, or add additional
* entry point hooks.
* @param {!Function} fn An entry point function to be protected.
* @return {!Function} protected wrapper function.
* @protected
*/
goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
'use strict';
var that = this;
var googDebugErrorHandlerProtectedFunction = function() {
'use strict';
var self = /** @type {?} */ (this);
if (that.isDisposed()) {
return fn.apply(self, arguments);
}
try {
return fn.apply(self, arguments);
} catch (e) {
that.handleError_(e);
}
};
googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
return googDebugErrorHandlerProtectedFunction;
};
/**
* Internal error handler.
* @param {?} e The error string or an Error-like object.
* @private
*/
goog.debug.ErrorHandler.prototype.handleError_ = function(e) {
'use strict';
// Don't re-report errors that have already been handled by this code.
var MESSAGE_PREFIX =
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX;
if ((e && typeof e === 'object' && typeof e.message === 'string' &&
e.message.indexOf(MESSAGE_PREFIX) == 0) ||
(typeof e === 'string' && e.indexOf(MESSAGE_PREFIX) == 0)) {
return;
}
this.errorHandlerFn_(e);
if (!this.wrapErrors_) {
// Add the prefix to the existing message.
if (this.prefixErrorMessages_) {
if (typeof e === 'object' && e && typeof e.message === 'string') {
/** @type {{message}} */ (e).message = MESSAGE_PREFIX + e.message;
} else {
e = MESSAGE_PREFIX + e;
}
}
if (goog.DEBUG) {
// Work around for https://code.google.com/p/v8/issues/detail?id=2625
// and https://code.google.com/p/chromium/issues/detail?id=237059
// Custom errors and errors with custom stack traces show the wrong
// stack trace
// If it has a stack and Error.captureStackTrace is supported (only
// supported in V8 as of May 2013) log the stack to the console.
if (e && typeof e.stack === 'string' && Error.captureStackTrace &&
goog.global['console']) {
goog.global['console']['error'](e.message, e.stack);
}
}
// Re-throw original error. This is great for debugging as it makes
// browser JS dev consoles show the correct error and stack trace.
throw e;
}
// Re-throw it since this may be expected by the caller.
throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
};
// TODO(mknichel): Allow these functions to take in the window to protect.
/**
* Installs exception protection for window.setTimeout to handle exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowSetTimeout = function() {
'use strict';
this.protectWindowFunctionsHelper_('setTimeout');
};
/**
* Install exception protection for window.setInterval to handle exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowSetInterval = function() {
'use strict';
this.protectWindowFunctionsHelper_('setInterval');
};
/**
* Install an unhandledrejection event listener that reports rejected promises.
* Note: this will only work with Chrome 49+ and friends, but so far is the only
* way to report uncaught errors in aysnc/await functions.
* @param {!Window=} win the window to instrument, defaults to current window
*/
goog.debug.ErrorHandler.prototype.catchUnhandledRejections = function(win) {
'use strict';
win = win || goog.global['window'];
if ('onunhandledrejection' in win) {
win.onunhandledrejection = (event) => {
// event.reason contains the rejection reason. When an Error is
// thrown, this is the Error object. If it is undefined, create a new
// error object.
const e =
event && event.reason ? event.reason : new Error('uncaught error');
this.handleError_(e);
};
}
};
/**
* Install exception protection for window.requestAnimationFrame to handle
* exceptions.
*/
goog.debug.ErrorHandler.prototype.protectWindowRequestAnimationFrame =
function() {
'use strict';
var win = goog.global['window'];
var fnNames = [
'requestAnimationFrame', 'mozRequestAnimationFrame', 'webkitAnimationFrame',
'msRequestAnimationFrame'
];
for (var i = 0; i < fnNames.length; i++) {
var fnName = fnNames[i];
if (fnNames[i] in win) {
this.protectWindowFunctionsHelper_(fnName);
}
}
};
/**
* Helper function for protecting a function that causes a function to be
* asynchronously called, for example setTimeout or requestAnimationFrame.
* @param {string} fnName The name of the function to protect.
* @private
*/
goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ = function(
fnName) {
'use strict';
var win = goog.global['window'];
var originalFn = win[fnName];
var that = this;
win[fnName] = function(fn, time) {
'use strict';
// Don't try to protect strings. In theory, we could try to globalEval
// the string, but this seems to lead to permission errors on IE6.
if (typeof fn === 'string') {
fn = goog.partial(goog.globalEval, fn);
}
arguments[0] = fn = that.protectEntryPoint(fn);
// IE doesn't support .call for setInterval/setTimeout, but it
// also doesn't care what "this" is, so we can just call the
// original function directly
if (originalFn.apply) {
return originalFn.apply(/** @type {?} */ (this), arguments);
} else {
var callback = fn;
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments, 2);
callback = function() {
'use strict';
fn.apply(/** @type {?} */ (this), args);
};
}
return originalFn(callback, time);
}
};
win[fnName][this.getFunctionIndex_(false)] = originalFn;
};
/**
* Set whether to wrap errors that occur in protected functions in a
* goog.debug.ErrorHandler.ProtectedFunctionError.
* @param {boolean} wrapErrors Whether to wrap errors.
*/
goog.debug.ErrorHandler.prototype.setWrapErrors = function(wrapErrors) {
'use strict';
this.wrapErrors_ = wrapErrors;
};
/**
* Set whether to add a prefix to all error messages that occur in protected
* functions.
* @param {boolean} prefixErrorMessages Whether to add a prefix to error
* messages.
*/
goog.debug.ErrorHandler.prototype.setPrefixErrorMessages = function(
prefixErrorMessages) {
'use strict';
this.prefixErrorMessages_ = prefixErrorMessages;
};
/** @override */
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
'use strict';
// Try to unwrap window.setTimeout and window.setInterval.
var win = goog.global['window'];
win.setTimeout = this.unwrap(win.setTimeout);
win.setInterval = this.unwrap(win.setInterval);
goog.debug.ErrorHandler.base(this, 'disposeInternal');
};
/**
* Error thrown to the caller of a protected entry point if the entry point
* throws an error.
* @param {*} cause The error thrown by the entry point.
* @constructor
* @extends {goog.debug.Error}
* @final
*/
goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
'use strict';
/** @suppress {missingProperties} message may not be defined. */
var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
(cause && cause.message ? String(cause.message) : String(cause));
goog.debug.ErrorHandler.ProtectedFunctionError.base(
this, 'constructor', message, /** @type {?} */ (cause));
/** @suppress {missingProperties} stack may not be defined. */
var stack = cause && cause.stack;
if (stack && typeof stack === 'string') {
this.stack = /** @type {string} */ (stack);
}
};
goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);
/**
* Text to prefix the message with.
* @type {string}
*/
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
'Error in protected function: ';
+285
View File
@@ -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;
};
+25
View File
@@ -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;
+34
View File
@@ -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;
+48
View File
@@ -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;
+398
View File
@@ -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;
};
+94
View File
@@ -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;
+3499
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -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() {};
+56
View File
@@ -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'
};
+40
View File
@@ -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
};
+941
View File
@@ -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 '';
};
+457
View File
@@ -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');
+34
View File
@@ -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;
};
+447
View File
@@ -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] || '';
};
+118
View File
@@ -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;
})
};
+125
View File
@@ -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();
};
+488
View File
@@ -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');
};
+41
View File
@@ -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;
};
+24
View File
@@ -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;
+960
View File
@@ -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_);
});
+495
View File
@@ -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;
};
+437
View File
@@ -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
};
+56
View File
@@ -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) {};
+265
View File
@@ -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) {};
+80
View File
@@ -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;
+124
View File
@@ -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;
};
+310
View File
@@ -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;
};
+79
View File
@@ -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');
}
};
+112
View File
@@ -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;
};
+584
View File
@@ -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';
};
+47
View File
@@ -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);
+21
View File
@@ -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'
};
+1006
View File
File diff suppressed because it is too large Load Diff
+1085
View File
File diff suppressed because it is too large Load Diff
+238
View File
@@ -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
* `&lt;` is dangerous, even when inside JavaScript strings, and so should
* always be forbidden or JavaScript escaped in user controlled input. For
* example, if `&lt;/script&gt;&lt;script&gt;evil&lt;/script&gt;"` 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 "&lt;" 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;
+585
View File
@@ -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
* &lt;style&gt; 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 &lt;style&gt; tag (where it can't
* be HTML escaped). For example, if the SafeStyle containing
* `font: 'foo &lt;style/&gt;&lt;script&gt;evil&lt;/script&gt;'` were
* interpolated within a &lt;style&gt; 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;
+297
View File
@@ -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 `&lt;` is dangerous, even when inside CSS strings, and so should
* always be forbidden or CSS-escaped in user controlled input. For example, if
* `&lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"` 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
* `&amp;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 &lt; 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;
+807
View File
@@ -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');
+524
View File
@@ -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;
};
+41
View File
@@ -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_;
};
+230
View File
@@ -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);
};
+943
View File
@@ -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;
+201
View File
@@ -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,
};
+1463
View File
File diff suppressed because it is too large Load Diff
+81
View File
@@ -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);
};
+395
View File
@@ -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('}');
};
+340
View File
@@ -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,
};
+69
View File
@@ -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();
};
+145
View File
@@ -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,
};
+181
View File
@@ -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,
};
+39
View File
@@ -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;
})();
+211
View File
@@ -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,
};
+1084
View File
File diff suppressed because it is too large Load Diff
+289
View File
@@ -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);
};
+892
View File
@@ -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_);
};
+903
View File
@@ -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);
+477
View File
@@ -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));
};
+235
View File
@@ -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);
};
+104
View File
@@ -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;
+506
View File
@@ -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_;
};
+23
View File
@@ -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();
+123
View File
@@ -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';
}
};
+34
View File
@@ -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',
};
+118
View File
@@ -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;
}
};
+64
View File
@@ -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_();
};
+1461
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -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();
};
+136
View File
@@ -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) {};
+818
View File
@@ -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_;
};
+250
View File
@@ -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());
+58
View File
@@ -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;
+707
View File
@@ -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,
};
+1447
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -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;
+123
View File
@@ -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;
}
};
+132
View File
@@ -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));
};
+186
View File
@@ -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('');
+394
View File
@@ -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_, '&amp;')
.replace(goog.string.internal.LT_RE_, '&lt;')
.replace(goog.string.internal.GT_RE_, '&gt;')
.replace(goog.string.internal.QUOT_RE_, '&quot;')
.replace(goog.string.internal.SINGLE_QUOTE_RE_, '&#39;')
.replace(goog.string.internal.NULL_RE_, '&#0;');
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_, '&amp;');
}
if (str.indexOf('<') != -1) {
str = str.replace(goog.string.internal.LT_RE_, '&lt;');
}
if (str.indexOf('>') != -1) {
str = str.replace(goog.string.internal.GT_RE_, '&gt;');
}
if (str.indexOf('"') != -1) {
str = str.replace(goog.string.internal.QUOT_RE_, '&quot;');
}
if (str.indexOf('\'') != -1) {
str = str.replace(goog.string.internal.SINGLE_QUOTE_RE_, '&#39;');
}
if (str.indexOf('\x00') != -1) {
str = str.replace(goog.string.internal.NULL_RE_, '&#0;');
}
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, ' &#160;'), 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;
};
+1512
View File
File diff suppressed because it is too large Load Diff
+101
View File
@@ -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_;
};
+40
View File
@@ -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;
+45
View File
@@ -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;
+348
View File
@@ -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_);
};
+560
View File
@@ -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);
};
+68
View File
@@ -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