Working production build in docs subdirectory.

This commit is contained in:
Simon Brooke 2020-02-27 09:35:17 +00:00
parent bb7be028e6
commit a5204c66b9
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
644 changed files with 134256 additions and 53616 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,369 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities to check the preconditions, postconditions and
* invariants runtime.
*
* Methods in this package should be given special treatment by the compiler
* for type-inference. For example, <code>goog.asserts.assert(foo)</code>
* will restrict <code>foo</code> to a truthy value.
*
* 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.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');
goog.require('goog.debug.Error');
goog.require('goog.dom.NodeType');
goog.require('goog.string');
/**
* @define {boolean} Whether to strip out asserts or to leave them in.
*/
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) {
messageArgs.unshift(messagePattern);
goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
// Remove the messagePattern afterwards to avoid permanently modifying the
// passed in array.
messageArgs.shift();
/**
* 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 */
goog.asserts.AssertionError.prototype.name = 'AssertionError';
/**
* The default error handler.
* @param {!goog.asserts.AssertionError} e The exception to be handled.
*/
goog.asserts.DEFAULT_ERROR_HANDLER = function(e) {
throw e;
};
/**
* The handler responsible for throwing or logging assertion errors.
* @private {function(!goog.asserts.AssertionError)}
*/
goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
/**
* 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) {
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
*/
goog.asserts.setErrorHandler = function(errorHandler) {
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.
*/
goog.asserts.assert = function(condition, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !condition) {
goog.asserts.doAssertFailure_(
'', null, opt_message, Array.prototype.slice.call(arguments, 2));
}
return condition;
};
/**
* 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.
* @throws {goog.asserts.AssertionError} Failure.
*/
goog.asserts.fail = function(opt_message, var_args) {
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.
*/
goog.asserts.assertNumber = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
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.
*/
goog.asserts.assertString = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
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.
*/
goog.asserts.assertFunction = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
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.
*/
goog.asserts.assertObject = function(value, opt_message, var_args) {
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.
*/
goog.asserts.assertArray = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.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.
*/
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
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.
*/
goog.asserts.assertElement = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS &&
(!goog.isObject(value) || 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.
*
* @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
*/
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
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 that no enumerable keys are present in Object.prototype. Such keys
* would break most code that use {@code for (var ... in ...)} loops.
*/
goog.asserts.assertObjectPrototypeIsIntact = function() {
for (var key in Object.prototype) {
goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
}
};
/**
* 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) {
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return value.constructor.displayName || value.constructor.name ||
Object.prototype.toString.call(value);
} else {
return value === null ? 'null' : typeof value;
}
};

View file

@ -0,0 +1,83 @@
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Simple 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 = goog.defineClass(null, {
/**
* @param {function():ITEM} create
* @param {function(ITEM):void} reset
* @param {number} limit
*/
constructor: function(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: function() {
var 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: function(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: function() { return this.occupants_; }
});

View file

@ -0,0 +1,265 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides a 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.provide('goog.async.throwException');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.dom.TagName');
goog.require('goog.functions');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.engine');
/**
* 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
*/
goog.async.throwException = function(exception) {
// Each throw needs to be in its own context.
goog.global.setTimeout(function() { throw exception; }, 0);
};
/**
* 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) {
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 (goog.isFunction(goog.global.setImmediate) &&
(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
*/
goog.async.nextTick.useSetImmediate_ = function() {
// 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() {
// 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() {
// Make an empty, invisible iframe.
var iframe = /** @type {!HTMLIFrameElement} */ (
document.createElement(String(goog.dom.TagName.IFRAME)));
iframe.style.display = 'none';
iframe.src = '';
document.documentElement.appendChild(iframe);
var win = iframe.contentWindow;
var doc = win.document;
doc.open();
doc.write('');
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) {
// 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() { 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() {
if (goog.isDef(head.next)) {
head = head.next;
var cb = head.cb;
head.cb = null;
cb();
}
};
return function(cb) {
tail.next = {cb: cb};
tail = tail.next;
channel['port2'].postMessage(0);
};
}
// Implementation for IE6 to IE10: Script elements fire an asynchronous
// onreadystatechange event when inserted into the DOM.
if (typeof document !== 'undefined' &&
'onreadystatechange' in
document.createElement(String(goog.dom.TagName.SCRIPT))) {
return function(cb) {
var script = document.createElement(String(goog.dom.TagName.SCRIPT));
script.onreadystatechange = function() {
// Clean up and call the callback.
script.onreadystatechange = null;
script.parentNode.removeChild(script);
script = null;
cb();
cb = null;
};
document.documentElement.appendChild(script);
};
}
// Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
// or more.
// NOTE(user): This fallback is used for IE11.
return function(cb) {
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) { goog.async.nextTick.wrapCallback_ = transformer; });

View file

@ -0,0 +1,136 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.async.run');
goog.require('goog.async.WorkQueue');
goog.require('goog.async.nextTick');
goog.require('goog.async.throwException');
/**
* 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) {
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() {
// If native Promises are available in the browser, just schedule the callback
// on a fulfilled promise, which is specified to be async, but as fast as
// possible. 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.
if (String(goog.global.Promise).indexOf('[native code]') != -1) {
var promise = goog.global.Promise.resolve(undefined);
goog.async.run.schedule_ = function() {
promise.then(goog.async.run.processWorkQueue);
};
} else {
goog.async.run.schedule_ = function() {
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) {
goog.async.run.schedule_ = function() {
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() {
goog.async.run.workQueueScheduled_ = false;
goog.async.run.workQueue_ = new goog.async.WorkQueue();
};
}
/**
* 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() {
// 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;
};

View file

@ -0,0 +1,138 @@
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.async.WorkItem');
goog.provide('goog.async.WorkQueue');
goog.require('goog.asserts');
goog.require('goog.async.FreeList');
// 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
* @constructor
* @final
* @struct
*/
goog.async.WorkQueue = function() {
this.workHead_ = null;
this.workTail_ = null;
};
/** @define {number} The maximum number of entries to keep for recycling. */
goog.define('goog.async.WorkQueue.DEFAULT_MAX_UNUSED', 100);
/** @const @private {goog.async.FreeList<goog.async.WorkItem>} */
goog.async.WorkQueue.freelist_ = new goog.async.FreeList(
function() { return new goog.async.WorkItem(); },
function(item) { item.reset(); }, goog.async.WorkQueue.DEFAULT_MAX_UNUSED);
/**
* @param {function()} fn
* @param {Object|null|undefined} scope
*/
goog.async.WorkQueue.prototype.add = function(fn, scope) {
var item = this.getUnusedItem_();
item.set(fn, scope);
if (this.workTail_) {
this.workTail_.next = item;
this.workTail_ = item;
} else {
goog.asserts.assert(!this.workHead_);
this.workHead_ = item;
this.workTail_ = item;
}
};
/**
* @return {goog.async.WorkItem}
*/
goog.async.WorkQueue.prototype.remove = function() {
var 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 {goog.async.WorkItem} item
*/
goog.async.WorkQueue.prototype.returnUnused = function(item) {
goog.async.WorkQueue.freelist_.put(item);
};
/**
* @return {goog.async.WorkItem}
* @private
*/
goog.async.WorkQueue.prototype.getUnusedItem_ = function() {
return goog.async.WorkQueue.freelist_.get();
};
/**
* @constructor
* @final
* @struct
*/
goog.async.WorkItem = function() {
/** @type {?function()} */
this.fn = null;
/** @type {Object|null|undefined} */
this.scope = null;
/** @type {?goog.async.WorkItem} */
this.next = null;
};
/**
* @param {function()} fn
* @param {Object|null|undefined} scope
*/
goog.async.WorkItem.prototype.set = function(fn, scope) {
this.fn = fn;
this.scope = scope;
this.next = null;
};
/** Reset the work item so they don't prevent GC before reuse */
goog.async.WorkItem.prototype.reset = function() {
this.fn = null;
this.scope = null;
this.next = null;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,359 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Base64 en/decoding. Not much to say here except that we
* work with decoded values in arrays of bytes. By "byte" I mean a number
* in [0, 255].
*
* @author doughtie@google.com (Gavin Doughtie)
*/
goog.provide('goog.crypt.base64');
goog.require('goog.asserts');
goog.require('goog.crypt');
goog.require('goog.string');
goog.require('goog.userAgent');
goog.require('goog.userAgent.product');
// Static lookup maps, lazily populated by init_()
/**
* Maps bytes to characters.
* @type {Object}
* @private
*/
goog.crypt.base64.byteToCharMap_ = null;
/**
* Maps characters to bytes. Used for normal and websafe characters.
* @type {Object}
* @private
*/
goog.crypt.base64.charToByteMap_ = null;
/**
* Maps bytes to websafe characters.
* @type {Object}
* @private
*/
goog.crypt.base64.byteToCharMapWebSafe_ = null;
/**
* Our default alphabet, shared between
* ENCODED_VALS and ENCODED_VALS_WEBSAFE
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS_BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789';
/**
* Our default alphabet. Value 64 (=) is special; it means "nothing."
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS = goog.crypt.base64.ENCODED_VALS_BASE + '+/=';
/**
* Our websafe alphabet.
* @type {string}
*/
goog.crypt.base64.ENCODED_VALS_WEBSAFE =
goog.crypt.base64.ENCODED_VALS_BASE + '-_.';
/**
* White list of implementations with known-good native atob and btoa functions.
* Listing these explicitly (via the ASSUME_* wrappers) benefits dead-code
* removal in per-browser compilations.
* @private {boolean}
*/
goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ = goog.userAgent.GECKO ||
(goog.userAgent.WEBKIT && !goog.userAgent.product.SAFARI) ||
goog.userAgent.OPERA;
/**
* Does this browser have a working btoa function?
* @private {boolean}
*/
goog.crypt.base64.HAS_NATIVE_ENCODE_ =
goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ ||
typeof(goog.global.btoa) == 'function';
/**
* Does this browser have a working atob function?
* We blacklist known-bad implementations:
* - IE (10+) added atob() but it does not tolerate whitespace on the input.
* @private {boolean}
*/
goog.crypt.base64.HAS_NATIVE_DECODE_ =
goog.crypt.base64.ASSUME_NATIVE_SUPPORT_ ||
(!goog.userAgent.product.SAFARI && !goog.userAgent.IE &&
typeof(goog.global.atob) == 'function');
/**
* Base64-encode an array of bytes.
*
* @param {Array<number>|Uint8Array} input An array of bytes (numbers with
* value in [0, 255]) to encode.
* @param {boolean=} opt_webSafe True indicates we should use the alternative
* alphabet, which does not require escaping for use in URLs.
* @return {string} The base64 encoded string.
*/
goog.crypt.base64.encodeByteArray = function(input, opt_webSafe) {
// Assert avoids runtime dependency on goog.isArrayLike, which helps reduce
// size of jscompiler output, and which yields slight performance increase.
goog.asserts.assert(
goog.isArrayLike(input), 'encodeByteArray takes an array as a parameter');
goog.crypt.base64.init_();
var byteToCharMap = opt_webSafe ? goog.crypt.base64.byteToCharMapWebSafe_ :
goog.crypt.base64.byteToCharMap_;
var output = [];
for (var i = 0; i < input.length; i += 3) {
var byte1 = input[i];
var haveByte2 = i + 1 < input.length;
var byte2 = haveByte2 ? input[i + 1] : 0;
var haveByte3 = i + 2 < input.length;
var byte3 = haveByte3 ? input[i + 2] : 0;
var outByte1 = byte1 >> 2;
var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
var outByte4 = byte3 & 0x3F;
if (!haveByte3) {
outByte4 = 64;
if (!haveByte2) {
outByte3 = 64;
}
}
output.push(
byteToCharMap[outByte1], byteToCharMap[outByte2],
byteToCharMap[outByte3], byteToCharMap[outByte4]);
}
return output.join('');
};
/**
* Base64-encode a string.
*
* @param {string} input A string to encode.
* @param {boolean=} opt_webSafe True indicates we should use the alternative
* alphabet, which does not require escaping for use in URLs.
* @return {string} The base64 encoded string.
*/
goog.crypt.base64.encodeString = function(input, opt_webSafe) {
// Shortcut for browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (goog.crypt.base64.HAS_NATIVE_ENCODE_ && !opt_webSafe) {
return goog.global.btoa(input);
}
return goog.crypt.base64.encodeByteArray(
goog.crypt.stringToByteArray(input), opt_webSafe);
};
/**
* Base64-decode a string.
*
* @param {string} input Input to decode. Any whitespace is ignored, and the
* input maybe encoded with either supported alphabet (or a mix thereof).
* @param {boolean=} opt_webSafe True indicates we should use the alternative
* alphabet, which does not require escaping for use in URLs. Note that
* passing false may also still allow webSafe input decoding, when the
* fallback decoder is used on browsers without native support.
* @return {string} string representing the decoded value.
*/
goog.crypt.base64.decodeString = function(input, opt_webSafe) {
// Shortcut for browsers that implement
// a native base64 encoder in the form of "btoa/atob"
if (goog.crypt.base64.HAS_NATIVE_DECODE_ && !opt_webSafe) {
return goog.global.atob(input);
}
var output = '';
function pushByte(b) { output += String.fromCharCode(b); }
goog.crypt.base64.decodeStringInternal_(input, pushByte);
return output;
};
/**
* Base64-decode a string to an Array of numbers.
*
* In base-64 decoding, groups of four characters are converted into three
* bytes. If the encoder did not apply padding, the input length may not
* be a multiple of 4.
*
* In this case, the last group will have fewer than 4 characters, and
* padding will be inferred. If the group has one or two characters, it decodes
* to one byte. If the group has three characters, it decodes to two bytes.
*
* @param {string} input Input to decode. Any whitespace is ignored, and the
* input maybe encoded with either supported alphabet (or a mix thereof).
* @param {boolean=} opt_ignored Unused parameter, retained for compatibility.
* @return {!Array<number>} bytes representing the decoded value.
*/
goog.crypt.base64.decodeStringToByteArray = function(input, opt_ignored) {
var output = [];
function pushByte(b) { output.push(b); }
goog.crypt.base64.decodeStringInternal_(input, pushByte);
return output;
};
/**
* Base64-decode a string to a Uint8Array.
*
* Note that Uint8Array is not supported on older browsers, e.g. IE < 10.
* @see http://caniuse.com/uint8array
*
* In base-64 decoding, groups of four characters are converted into three
* bytes. If the encoder did not apply padding, the input length may not
* be a multiple of 4.
*
* In this case, the last group will have fewer than 4 characters, and
* padding will be inferred. If the group has one or two characters, it decodes
* to one byte. If the group has three characters, it decodes to two bytes.
*
* @param {string} input Input to decode. Any whitespace is ignored, and the
* input maybe encoded with either supported alphabet (or a mix thereof).
* @return {!Uint8Array} bytes representing the decoded value.
*/
goog.crypt.base64.decodeStringToUint8Array = function(input) {
goog.asserts.assert(
!goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10'),
'Browser does not support typed arrays');
var output = new Uint8Array(Math.ceil(input.length * 3 / 4));
var outLen = 0;
function pushByte(b) { output[outLen++] = b; }
goog.crypt.base64.decodeStringInternal_(input, pushByte);
return output.subarray(0, outLen);
};
/**
* @param {string} input Input to decode.
* @param {function(number):void} pushByte result accumulator.
* @private
*/
goog.crypt.base64.decodeStringInternal_ = function(input, pushByte) {
goog.crypt.base64.init_();
var nextCharIndex = 0;
/**
* @param {number} default_val Used for end-of-input.
* @return {number} The next 6-bit value, or the default for end-of-input.
*/
function getByte(default_val) {
while (nextCharIndex < input.length) {
var ch = input.charAt(nextCharIndex++);
var b = goog.crypt.base64.charToByteMap_[ch];
if (b != null) {
return b; // Common case: decoded the char.
}
if (!goog.string.isEmptyOrWhitespace(ch)) {
throw Error('Unknown base64 encoding at char: ' + ch);
}
// We encountered whitespace: loop around to the next input char.
}
return default_val; // No more input remaining.
}
while (true) {
var byte1 = getByte(-1);
var byte2 = getByte(0);
var byte3 = getByte(64);
var byte4 = getByte(64);
// The common case is that all four bytes are present, so if we have byte4
// we can skip over the truncated input special case handling.
if (byte4 === 64) {
if (byte1 === -1) {
return; // Terminal case: no input left to decode.
}
// Here we know an intermediate number of bytes are missing.
// The defaults for byte2, byte3 and byte4 apply the inferred padding
// rules per the public API documentation. i.e: 1 byte
// missing should yield 2 bytes of output, but 2 or 3 missing bytes yield
// a single byte of output. (Recall that 64 corresponds the padding char).
}
var outByte1 = (byte1 << 2) | (byte2 >> 4);
pushByte(outByte1);
if (byte3 != 64) {
var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2);
pushByte(outByte2);
if (byte4 != 64) {
var outByte3 = ((byte3 << 6) & 0xC0) | byte4;
pushByte(outByte3);
}
}
}
};
/**
* Lazy static initialization function. Called before
* accessing any of the static map variables.
* @private
*/
goog.crypt.base64.init_ = function() {
if (!goog.crypt.base64.byteToCharMap_) {
goog.crypt.base64.byteToCharMap_ = {};
goog.crypt.base64.charToByteMap_ = {};
goog.crypt.base64.byteToCharMapWebSafe_ = {};
// We want quick mappings back and forth, so we precompute two maps.
for (var i = 0; i < goog.crypt.base64.ENCODED_VALS.length; i++) {
goog.crypt.base64.byteToCharMap_[i] =
goog.crypt.base64.ENCODED_VALS.charAt(i);
goog.crypt.base64.charToByteMap_[goog.crypt.base64.byteToCharMap_[i]] = i;
goog.crypt.base64.byteToCharMapWebSafe_[i] =
goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i);
// Be forgiving when decoding and correctly decode both encodings.
if (i >= goog.crypt.base64.ENCODED_VALS_BASE.length) {
goog.crypt.base64
.charToByteMap_[goog.crypt.base64.ENCODED_VALS_WEBSAFE.charAt(i)] =
i;
}
}
}
};

View file

@ -0,0 +1,195 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Namespace with crypto related helper functions.
*/
goog.provide('goog.crypt');
goog.require('goog.array');
goog.require('goog.asserts');
/**
* Turns a string into an array of bytes; a "byte" being a JS number in the
* range 0-255. Multi-byte characters are written as little-endian.
* @param {string} str String value to arrify.
* @return {!Array<number>} Array of numbers corresponding to the
* UCS character codes of each character in str.
*/
goog.crypt.stringToByteArray = function(str) {
var output = [], p = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
// NOTE: c <= 0xffff since JavaScript strings are UTF-16.
if (c > 0xff) {
output[p++] = c & 0xff;
c >>= 8;
}
output[p++] = c;
}
return output;
};
/**
* Turns an array of numbers into the string given by the concatenation of the
* characters to which the numbers correspond.
* @param {!Uint8Array|!Array<number>} bytes Array of numbers representing
* characters.
* @return {string} Stringification of the array.
*/
goog.crypt.byteArrayToString = function(bytes) {
var CHUNK_SIZE = 8192;
// Special-case the simple case for speed's sake.
if (bytes.length <= CHUNK_SIZE) {
return String.fromCharCode.apply(null, bytes);
}
// The remaining logic splits conversion by chunks since
// Function#apply() has a maximum parameter count.
// See discussion: http://goo.gl/LrWmZ9
var str = '';
for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
str += String.fromCharCode.apply(null, chunk);
}
return str;
};
/**
* Turns an array of numbers into the hex string given by the concatenation of
* the hex values to which the numbers correspond.
* @param {Uint8Array|Array<number>} array Array of numbers representing
* characters.
* @return {string} Hex string.
*/
goog.crypt.byteArrayToHex = function(array) {
return goog.array
.map(
array,
function(numByte) {
var hexByte = numByte.toString(16);
return hexByte.length > 1 ? hexByte : '0' + hexByte;
})
.join('');
};
/**
* Converts a hex string into an integer array.
* @param {string} hexString Hex string of 16-bit integers (two characters
* per integer).
* @return {!Array<number>} Array of {0,255} integers for the given string.
*/
goog.crypt.hexToByteArray = function(hexString) {
goog.asserts.assert(
hexString.length % 2 == 0, 'Key string length must be multiple of 2');
var arr = [];
for (var i = 0; i < hexString.length; i += 2) {
arr.push(parseInt(hexString.substring(i, i + 2), 16));
}
return arr;
};
/**
* Converts a JS string to a UTF-8 "byte" array.
* @param {string} str 16-bit unicode string.
* @return {!Array<number>} UTF-8 byte array.
*/
goog.crypt.stringToUtf8ByteArray = function(str) {
// TODO(user): Use native implementations if/when available
var out = [], p = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 128) {
out[p++] = c;
} else if (c < 2048) {
out[p++] = (c >> 6) | 192;
out[p++] = (c & 63) | 128;
} else if (
((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
// Surrogate Pair
c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
out[p++] = (c >> 18) | 240;
out[p++] = ((c >> 12) & 63) | 128;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
} else {
out[p++] = (c >> 12) | 224;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
}
}
return out;
};
/**
* Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
* @param {Uint8Array|Array<number>} bytes UTF-8 byte array.
* @return {string} 16-bit Unicode string.
*/
goog.crypt.utf8ByteArrayToString = function(bytes) {
// TODO(user): Use native implementations if/when available
var out = [], pos = 0, c = 0;
while (pos < bytes.length) {
var c1 = bytes[pos++];
if (c1 < 128) {
out[c++] = String.fromCharCode(c1);
} else if (c1 > 191 && c1 < 224) {
var c2 = bytes[pos++];
out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
} else if (c1 > 239 && c1 < 365) {
// Surrogate Pair
var c2 = bytes[pos++];
var c3 = bytes[pos++];
var c4 = bytes[pos++];
var u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) -
0x10000;
out[c++] = String.fromCharCode(0xD800 + (u >> 10));
out[c++] = String.fromCharCode(0xDC00 + (u & 1023));
} else {
var c2 = bytes[pos++];
var c3 = bytes[pos++];
out[c++] =
String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
}
}
return out.join('');
};
/**
* XOR two byte arrays.
* @param {!Uint8Array|!Int8Array|!Array<number>} bytes1 Byte array 1.
* @param {!Uint8Array|!Int8Array|!Array<number>} bytes2 Byte array 2.
* @return {!Array<number>} Resulting XOR of the two byte arrays.
*/
goog.crypt.xorByteArray = function(bytes1, bytes2) {
goog.asserts.assert(
bytes1.length == bytes2.length, 'XOR array lengths must match');
var result = [];
for (var i = 0; i < bytes1.length; i++) {
result.push(bytes1[i] ^ bytes2[i]);
}
return result;
};

View file

@ -0,0 +1,632 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Logging and debugging utilities.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug');
goog.require('goog.array');
goog.require('goog.debug.errorcontext');
goog.require('goog.userAgent');
/** @define {boolean} Whether logging should be enabled. */
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
/** @define {boolean} Whether to force "sloppy" stack building. */
goog.define('goog.debug.FORCE_SLOPPY_STACKS', 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.
*/
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
var target = opt_target || goog.global;
var oldErrorHandler = target.onerror;
var retVal = !!opt_cancel;
// Chrome interprets onerror return value backwards (http://crbug.com/92062)
// until it was fixed in webkit revision r94061 (Webkit 535.3). This
// workaround still needs to be skipped in Safari after the webkit change
// gets pushed out in Safari.
// See https://bugs.webkit.org/show_bug.cgi?id=67119
if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.3')) {
retVal = !retVal;
}
/**
* New onerror handler for this target. This onerror handler follows the spec
* according to
* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
* The spec was changed in August 2013 to support receiving column information
* and an error object for all scripts on the same origin or cross origin
* scripts with the proper headers. See
* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
*
* @param {string} message The error message. For cross-origin errors, this
* will be scrubbed to just "Script error.". For new browsers that have
* updated to follow the latest spec, errors that come from origins that
* have proper cross origin headers will not be scrubbed.
* @param {string} url The URL of the script that caused the error. The URL
* will be scrubbed to "" for cross origin scripts unless the script has
* proper cross origin headers and the browser has updated to the latest
* spec.
* @param {number} line The line number in the script that the error
* occurred on.
* @param {number=} opt_col The optional column number that the error
* occurred on. Only browsers that have updated to the latest spec will
* include this.
* @param {Error=} opt_error The optional actual error object for this
* error that should include the stack. Only browsers that have updated
* to the latest spec will inlude this parameter.
* @return {boolean} Whether to prevent the error from reaching the browser.
*/
target.onerror = function(message, url, line, opt_col, opt_error) {
if (oldErrorHandler) {
oldErrorHandler(message, url, line, opt_col, opt_error);
}
logFunc({
message: message,
fileName: url,
line: line,
col: opt_col,
error: opt_error
});
return retVal;
};
};
/**
* Creates a string representing an object and all its properties.
* @param {Object|null|undefined} obj Object to expose.
* @param {boolean=} opt_showFn Show the functions as well as the properties,
* default is false.
* @return {string} The string representation of {@code obj}.
*/
goog.debug.expose = function(obj, opt_showFn) {
if (typeof obj == 'undefined') {
return 'undefined';
}
if (obj == null) {
return 'NULL';
}
var str = [];
for (var x in obj) {
if (!opt_showFn && goog.isFunction(obj[x])) {
continue;
}
var s = x + ' = ';
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 {@code obj}.
*/
goog.debug.deepExpose = function(obj, opt_showFn) {
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) {
var nestspace = space + ' ';
var indentMultiline = function(str) {
return str.replace(/\n/g, '\n' + space);
};
try {
if (!goog.isDef(obj)) {
str.push('undefined');
} else if (goog.isNull(obj)) {
str.push('NULL');
} else if (goog.isString(obj)) {
str.push('"' + indentMultiline(obj) + '"');
} else if (goog.isFunction(obj)) {
str.push(indentMultiline(String(obj)));
} else if (goog.isObject(obj)) {
// 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 && goog.isFunction(obj[x])) {
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) {
var str = [];
for (var i = 0; i < arr.length; i++) {
if (goog.isArray(arr[i])) {
str.push(goog.debug.exposeArray(arr[i]));
} else {
str.push(arr[i]);
}
}
return '[ ' + str.join(', ') + ' ]';
};
/**
* Normalizes the error/exception object between browsers.
* @param {*} err Raw error object.
* @return {!{
* message: (?|undefined),
* name: (?|undefined),
* lineNumber: (?|undefined),
* fileName: (?|undefined),
* stack: (?|undefined)
* }} Normalized error object.
*/
goog.debug.normalizeErrorObject = function(err) {
var href = goog.getObjectByName('window.location.href');
if (goog.isString(err)) {
return {
'message': err,
'name': 'Unknown error',
'lineNumber': 'Not available',
'fileName': href,
'stack': 'Not available'
};
}
var lineNumber, fileName;
var threwError = false;
try {
lineNumber = err.lineNumber || err.line || 'Not available';
} catch (e) {
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
// Message: Permission denied to get property UnnamedClass.lineNumber
lineNumber = 'Not available';
threwError = true;
}
try {
fileName = err.fileName || err.filename || err.sourceURL ||
// $googDebugFname may be set before a call to eval to set the filename
// that the eval is supposed to present.
goog.global['$googDebugFname'] || href;
} catch (e) {
// Firefox 2 may also throw an error when accessing 'filename'.
fileName = 'Not available';
threwError = true;
}
// The IE Error object contains only the name and the message.
// The Safari Error object uses the line and sourceURL fields.
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
!err.message || !err.name) {
return {
'message': err.message || 'Not available',
'name': err.name || 'UnknownError',
'lineNumber': lineNumber,
'fileName': fileName,
'stack': err.stack || 'Not available'
};
}
// Standards error object
// Typed !Object. Should be a subtype of the return type, but it's not.
return /** @type {?} */ (err);
};
/**
* 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) {
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) {
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) {
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) {
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) {
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) {
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('');
};
/**
* Set a custom function name resolver.
* @param {function(Function): string} resolver Resolves functions to their
* names.
*/
goog.debug.setFunctionResolver = function(resolver) {
goog.debug.fnNameResolver_ = resolver;
};
/**
* Gets a function name
* @param {Function} fn Function to get name of.
* @return {string} Function's name.
*/
goog.debug.getFunctionName = function(fn) {
if (goog.debug.fnNameCache_[fn]) {
return goog.debug.fnNameCache_[fn];
}
if (goog.debug.fnNameResolver_) {
var name = goog.debug.fnNameResolver_(fn);
if (name) {
goog.debug.fnNameCache_[fn] = name;
return name;
}
}
// Heuristically determine function name based on code.
var functionSource = String(fn);
if (!goog.debug.fnNameCache_[functionSource]) {
var matches = /function ([^\(]+)/.exec(functionSource);
if (matches) {
var method = matches[1];
goog.debug.fnNameCache_[functionSource] = method;
} else {
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
}
}
return goog.debug.fnNameCache_[functionSource];
};
/**
* Makes whitespace visible by replacing it with printable characters.
* This is useful in finding diffrences between the expected and the actual
* output strings of a testcase.
* @param {string} string whose whitespace needs to be made visible.
* @return {string} string whose whitespace is made visible.
*/
goog.debug.makeWhitespaceVisible = function(string) {
return string.replace(/ /g, '[_]')
.replace(/\f/g, '[f]')
.replace(/\n/g, '[n]\n')
.replace(/\r/g, '[r]')
.replace(/\t/g, '[t]');
};
/**
* 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) {
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return 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_ = {};
/**
* Resolves functions to their names. Resolved function names will be cached.
* @type {function(Function):string}
* @private
*/
goog.debug.fnNameResolver_;

View file

@ -0,0 +1,159 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A global registry for entry points into a program,
* so that they can be instrumented. Each module should register their
* entry points with this registry. Designed to be compiled out
* if no instrumentation is requested.
*
* Entry points may be registered before or after a call to
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
* later, the existing monitor will instrument the new entry point.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');
goog.require('goog.asserts');
/**
* @interface
*/
goog.debug.EntryPointMonitor = function() {};
/**
* Instruments a function.
*
* @param {!Function} fn A function to instrument.
* @return {!Function} The instrumented function.
*/
goog.debug.EntryPointMonitor.prototype.wrap;
/**
* Try to remove an instrumentation wrapper created by this monitor.
* If the function passed to unwrap is not a wrapper created by this
* monitor, then we will do nothing.
*
* Notice that some wrappers may not be unwrappable. For example, if other
* monitors have applied their own wrappers, then it will be impossible to
* unwrap them because their wrappers will have captured our wrapper.
*
* So it is important that entry points are unwrapped in the reverse
* order that they were wrapped.
*
* @param {!Function} fn A function to unwrap.
* @return {!Function} The unwrapped function, or {@code fn} if it was not
* a wrapped function created by this monitor.
*/
goog.debug.EntryPointMonitor.prototype.unwrap;
/**
* An array of entry point callbacks.
* @type {!Array<function(!Function)>}
* @private
*/
goog.debug.entryPointRegistry.refList_ = [];
/**
* Monitors that should wrap all the entry points.
* @type {!Array<!goog.debug.EntryPointMonitor>}
* @private
*/
goog.debug.entryPointRegistry.monitors_ = [];
/**
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
* Checking this allows the compiler to optimize out the registrations.
* @type {boolean}
* @private
*/
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
/**
* Register an entry point with this module.
*
* The entry point will be instrumented when a monitor is passed to
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
* entry point is instrumented immediately.
*
* @param {function(!Function)} callback A callback function which is called
* with a transforming function to instrument the entry point. The callback
* is responsible for wrapping the relevant entry point with the
* transforming function.
*/
goog.debug.entryPointRegistry.register = function(callback) {
// Don't use push(), so that this can be compiled out.
goog.debug.entryPointRegistry
.refList_[goog.debug.entryPointRegistry.refList_.length] = callback;
// If no one calls monitorAll, this can be compiled out.
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
var monitors = goog.debug.entryPointRegistry.monitors_;
for (var i = 0; i < monitors.length; i++) {
callback(goog.bind(monitors[i].wrap, monitors[i]));
}
}
};
/**
* Configures a monitor to wrap all entry points.
*
* Entry points that have already been registered are immediately wrapped by
* the monitor. When an entry point is registered in the future, it will also
* be wrapped by the monitor when it is registered.
*
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
*/
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
var transformer = goog.bind(monitor.wrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
goog.debug.entryPointRegistry.monitors_.push(monitor);
};
/**
* Try to unmonitor all the entry points that have already been registered. If
* an entry point is registered in the future, it will not be wrapped by the
* monitor when it is registered. Note that this may fail if the entry points
* have additional wrapping.
*
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
* the entry points.
* @throws {Error} If the monitor is not the most recently configured monitor.
*/
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
var monitors = goog.debug.entryPointRegistry.monitors_;
goog.asserts.assert(
monitor == monitors[monitors.length - 1],
'Only the most recent monitor can be unwrapped.');
var transformer = goog.bind(monitor.unwrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
monitors.length--;
};

View file

@ -0,0 +1,63 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides a base class for custom Error objects such that the
* stack is correctly maintained.
*
* You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
* sufficient.
*
*/
goog.provide('goog.debug.Error');
/**
* Base class for custom error objects.
* @param {*=} opt_msg The message associated with the error.
* @constructor
* @extends {Error}
*/
goog.debug.Error = function(opt_msg) {
// Attempt to ensure there is a stack trace.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, goog.debug.Error);
} else {
var stack = new Error().stack;
if (stack) {
this.stack = stack;
}
}
if (opt_msg) {
this.message = String(opt_msg);
}
/**
* 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(goog.debug.Error, Error);
/** @override */
goog.debug.Error.prototype.name = 'CustomError';

View file

@ -0,0 +1,49 @@
// Copyright 2017 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides 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) {
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) {
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';

View file

@ -0,0 +1,148 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A buffer for log records. The purpose of this is to improve
* logging performance by re-using old objects when the buffer becomes full and
* to eliminate the need for each app to implement their own log buffer. The
* disadvantage to doing this is that log handlers cannot maintain references to
* log records and expect that they are not overwriten at a later point.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.debug.LogBuffer');
goog.require('goog.asserts');
goog.require('goog.debug.LogRecord');
/**
* Creates the log buffer.
* @constructor
* @final
*/
goog.debug.LogBuffer = function() {
goog.asserts.assert(
goog.debug.LogBuffer.isBufferingEnabled(),
'Cannot use goog.debug.LogBuffer without defining ' +
'goog.debug.LogBuffer.CAPACITY.');
this.clear();
};
/**
* A static method that always returns the same instance of LogBuffer.
* @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
*/
goog.debug.LogBuffer.getInstance = function() {
if (!goog.debug.LogBuffer.instance_) {
// This function is written with the return statement after the assignment
// to avoid the jscompiler StripCode bug described in http://b/2608064.
// After that bug is fixed this can be refactored.
goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
}
return goog.debug.LogBuffer.instance_;
};
/**
* @define {number} The number of log records to buffer. 0 means disable
* buffering.
*/
goog.define('goog.debug.LogBuffer.CAPACITY', 0);
/**
* The array to store the records.
* @type {!Array<!goog.debug.LogRecord|undefined>}
* @private
*/
goog.debug.LogBuffer.prototype.buffer_;
/**
* The index of the most recently added record or -1 if there are no records.
* @type {number}
* @private
*/
goog.debug.LogBuffer.prototype.curIndex_;
/**
* Whether the buffer is at capacity.
* @type {boolean}
* @private
*/
goog.debug.LogBuffer.prototype.isFull_;
/**
* Adds a log record to the buffer, possibly overwriting the oldest record.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @return {!goog.debug.LogRecord} The log record.
*/
goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
this.curIndex_ = curIndex;
if (this.isFull_) {
var ret = this.buffer_[curIndex];
ret.reset(level, msg, loggerName);
return ret;
}
this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
return this.buffer_[curIndex] =
new goog.debug.LogRecord(level, msg, loggerName);
};
/**
* @return {boolean} Whether the log buffer is enabled.
*/
goog.debug.LogBuffer.isBufferingEnabled = function() {
return goog.debug.LogBuffer.CAPACITY > 0;
};
/**
* Removes all buffered log records.
*/
goog.debug.LogBuffer.prototype.clear = function() {
this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
this.curIndex_ = -1;
this.isFull_ = false;
};
/**
* Calls the given function for each buffered log record, starting with the
* oldest one.
* @param {function(!goog.debug.LogRecord)} func The function to call.
*/
goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
var buffer = this.buffer_;
// Corner case: no records.
if (!buffer[0]) {
return;
}
var curIndex = this.curIndex_;
var i = this.isFull_ ? curIndex : -1;
do {
i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
} while (i != curIndex);
};

View file

@ -0,0 +1,865 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the Logger class. Please minimize dependencies
* this file has on other closure classes as any dependency it takes won't be
* able to use the logging infrastructure.
*
* @see ../demos/debug.html
*/
goog.provide('goog.debug.LogManager');
goog.provide('goog.debug.Loggable');
goog.provide('goog.debug.Logger');
goog.provide('goog.debug.Logger.Level');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug');
goog.require('goog.debug.LogBuffer');
goog.require('goog.debug.LogRecord');
/**
* A message value that can be handled by a Logger.
*
* Functions are treated like callbacks, but are only called when the event's
* log level is enabled. This is useful for logging messages that are expensive
* to construct.
*
* @typedef {string|function(): string}
*/
goog.debug.Loggable;
/**
* The Logger is an object used for logging debug messages. Loggers are
* normally named, using a hierarchical dot-separated namespace. Logger names
* can be arbitrary strings, but they should normally be based on the package
* name or class name of the logged component, such as goog.net.BrowserChannel.
*
* The Logger object is loosely based on the java class
* java.util.logging.Logger. It supports different levels of filtering for
* different loggers.
*
* The logger object should never be instantiated by application code. It
* should always use the goog.debug.Logger.getLogger function.
*
* @constructor
* @param {string} name The name of the Logger.
* @final
*/
goog.debug.Logger = function(name) {
/**
* Name of the Logger. Generally a dot-separated namespace
* @private {string}
*/
this.name_ = name;
/**
* Parent Logger.
* @private {goog.debug.Logger}
*/
this.parent_ = null;
/**
* Level that this logger only filters above. Null indicates it should
* inherit from the parent.
* @private {goog.debug.Logger.Level}
*/
this.level_ = null;
/**
* Map of children loggers. The keys are the leaf names of the children and
* the values are the child loggers.
* @private {Object}
*/
this.children_ = null;
/**
* Handlers that are listening to this logger.
* @private {Array<Function>}
*/
this.handlers_ = null;
};
/** @const */
goog.debug.Logger.ROOT_LOGGER_NAME = '';
/**
* @define {boolean} Toggles whether loggers other than the root logger can have
* log handlers attached to them and whether they can have their log level
* set. Logging is a bit faster when this is set to false.
*/
goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
/**
* @type {!Array<Function>}
* @private
*/
goog.debug.Logger.rootHandlers_ = [];
/**
* @type {goog.debug.Logger.Level}
* @private
*/
goog.debug.Logger.rootLevel_;
}
/**
* The Level class defines a set of standard logging levels that
* can be used to control logging output. The logging Level objects
* are ordered and are specified by ordered integers. Enabling logging
* at a given level also enables logging at all higher levels.
* <p>
* Clients should normally use the predefined Level constants such
* as Level.SEVERE.
* <p>
* The levels in descending order are:
* <ul>
* <li>SEVERE (highest value)
* <li>WARNING
* <li>INFO
* <li>CONFIG
* <li>FINE
* <li>FINER
* <li>FINEST (lowest value)
* </ul>
* In addition there is a level OFF that can be used to turn
* off logging, and a level ALL that can be used to enable
* logging of all messages.
*
* @param {string} name The name of the level.
* @param {number} value The numeric value of the level.
* @constructor
* @final
*/
goog.debug.Logger.Level = function(name, value) {
/**
* The name of the level
* @type {string}
*/
this.name = name;
/**
* The numeric value of the level
* @type {number}
*/
this.value = value;
};
/**
* @return {string} String representation of the logger level.
* @override
*/
goog.debug.Logger.Level.prototype.toString = function() {
return this.name;
};
/**
* OFF is a special level that can be used to turn off logging.
* This level is initialized to <CODE>Infinity</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.OFF = new goog.debug.Logger.Level('OFF', Infinity);
/**
* SHOUT is a message level for extra debugging loudness.
* This level is initialized to <CODE>1200</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
/**
* SEVERE is a message level indicating a serious failure.
* This level is initialized to <CODE>1000</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
/**
* WARNING is a message level indicating a potential problem.
* This level is initialized to <CODE>900</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
/**
* INFO is a message level for informational messages.
* This level is initialized to <CODE>800</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
/**
* CONFIG is a message level for static configuration messages.
* This level is initialized to <CODE>700</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
/**
* FINE is a message level providing tracing information.
* This level is initialized to <CODE>500</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
/**
* FINER indicates a fairly detailed tracing message.
* This level is initialized to <CODE>400</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
/**
* FINEST indicates a highly detailed tracing message.
* This level is initialized to <CODE>300</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
/**
* ALL indicates that all messages should be logged.
* This level is initialized to <CODE>0</CODE>.
* @type {!goog.debug.Logger.Level}
*/
goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
/**
* The predefined levels.
* @type {!Array<!goog.debug.Logger.Level>}
* @final
*/
goog.debug.Logger.Level.PREDEFINED_LEVELS = [
goog.debug.Logger.Level.OFF, goog.debug.Logger.Level.SHOUT,
goog.debug.Logger.Level.SEVERE, goog.debug.Logger.Level.WARNING,
goog.debug.Logger.Level.INFO, goog.debug.Logger.Level.CONFIG,
goog.debug.Logger.Level.FINE, goog.debug.Logger.Level.FINER,
goog.debug.Logger.Level.FINEST, goog.debug.Logger.Level.ALL
];
/**
* A lookup map used to find the level object based on the name or value of
* the level object.
* @type {Object}
* @private
*/
goog.debug.Logger.Level.predefinedLevelsCache_ = null;
/**
* Creates the predefined levels cache and populates it.
* @private
*/
goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
goog.debug.Logger.Level.predefinedLevelsCache_ = {};
for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
i++) {
goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
}
};
/**
* Gets the predefined level with the given name.
* @param {string} name The name of the level.
* @return {goog.debug.Logger.Level} The level, or null if none found.
*/
goog.debug.Logger.Level.getPredefinedLevel = function(name) {
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
goog.debug.Logger.Level.createPredefinedLevelsCache_();
}
return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
};
/**
* Gets the highest predefined level <= #value.
* @param {number} value Level value.
* @return {goog.debug.Logger.Level} The level, or null if none found.
*/
goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
goog.debug.Logger.Level.createPredefinedLevelsCache_();
}
if (value in /** @type {!Object} */ (
goog.debug.Logger.Level.predefinedLevelsCache_)) {
return goog.debug.Logger.Level.predefinedLevelsCache_[value];
}
for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
if (level.value <= value) {
return level;
}
}
return null;
};
/**
* Finds or creates a logger for a named subsystem. If a logger has already been
* created with the given name it is returned. Otherwise a new logger is
* created. If a new logger is created its log level will be configured based
* on the LogManager configuration and it will configured to also send logging
* output to its parent's handlers. It will be registered in the LogManager
* global namespace.
*
* @param {string} name A name for the logger. This should be a dot-separated
* name and should normally be based on the package name or class name of the
* subsystem, such as goog.net.BrowserChannel.
* @return {!goog.debug.Logger} The named logger.
* @deprecated use {@link goog.log} instead.
*/
goog.debug.Logger.getLogger = function(name) {
return goog.debug.LogManager.getLogger(name);
};
/**
* Logs a message to profiling tools, if available.
* {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
* {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
* @param {string} msg The message to log.
*/
goog.debug.Logger.logToProfilers = function(msg) {
// Using goog.global, as loggers might be used in window-less contexts.
var console = goog.global['console'];
if (console && console['timeStamp']) {
// Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
console['timeStamp'](msg);
}
var msWriteProfilerMark = goog.global['msWriteProfilerMark'];
if (msWriteProfilerMark) {
// Logs a message to the Microsoft profiler
msWriteProfilerMark(msg);
}
};
/**
* Gets the name of this logger.
* @return {string} The name of this logger.
*/
goog.debug.Logger.prototype.getName = function() {
return this.name_;
};
/**
* Adds a handler to the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {Function} handler Handler function to add.
*/
goog.debug.Logger.prototype.addHandler = function(handler) {
if (goog.debug.LOGGING_ENABLED) {
if (goog.debug.Logger.ENABLE_HIERARCHY) {
if (!this.handlers_) {
this.handlers_ = [];
}
this.handlers_.push(handler);
} else {
goog.asserts.assert(
!this.name_, 'Cannot call addHandler on a non-root logger when ' +
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
goog.debug.Logger.rootHandlers_.push(handler);
}
}
};
/**
* Removes a handler from the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {Function} handler Handler function to remove.
* @return {boolean} Whether the handler was removed.
*/
goog.debug.Logger.prototype.removeHandler = function(handler) {
if (goog.debug.LOGGING_ENABLED) {
var handlers = goog.debug.Logger.ENABLE_HIERARCHY ?
this.handlers_ :
goog.debug.Logger.rootHandlers_;
return !!handlers && goog.array.remove(handlers, handler);
} else {
return false;
}
};
/**
* Returns the parent of this logger.
* @return {goog.debug.Logger} The parent logger or null if this is the root.
*/
goog.debug.Logger.prototype.getParent = function() {
return this.parent_;
};
/**
* Returns the children of this logger as a map of the child name to the logger.
* @return {!Object} The map where the keys are the child leaf names and the
* values are the Logger objects.
*/
goog.debug.Logger.prototype.getChildren = function() {
if (!this.children_) {
this.children_ = {};
}
return this.children_;
};
/**
* Set the log level specifying which message levels will be logged by this
* logger. Message levels lower than this value will be discarded.
* The level value Level.OFF can be used to turn off logging. If the new level
* is null, it means that this node should inherit its level from its nearest
* ancestor with a specific (non-null) level value.
*
* @param {goog.debug.Logger.Level} level The new level.
*/
goog.debug.Logger.prototype.setLevel = function(level) {
if (goog.debug.LOGGING_ENABLED) {
if (goog.debug.Logger.ENABLE_HIERARCHY) {
this.level_ = level;
} else {
goog.asserts.assert(
!this.name_, 'Cannot call setLevel() on a non-root logger when ' +
'goog.debug.Logger.ENABLE_HIERARCHY is false.');
goog.debug.Logger.rootLevel_ = level;
}
}
};
/**
* Gets the log level specifying which message levels will be logged by this
* logger. Message levels lower than this value will be discarded.
* The level value Level.OFF can be used to turn off logging. If the level
* is null, it means that this node should inherit its level from its nearest
* ancestor with a specific (non-null) level value.
*
* @return {goog.debug.Logger.Level} The level.
*/
goog.debug.Logger.prototype.getLevel = function() {
return goog.debug.LOGGING_ENABLED ? this.level_ : goog.debug.Logger.Level.OFF;
};
/**
* Returns the effective level of the logger based on its ancestors' levels.
* @return {goog.debug.Logger.Level} The level.
*/
goog.debug.Logger.prototype.getEffectiveLevel = function() {
if (!goog.debug.LOGGING_ENABLED) {
return goog.debug.Logger.Level.OFF;
}
if (!goog.debug.Logger.ENABLE_HIERARCHY) {
return goog.debug.Logger.rootLevel_;
}
if (this.level_) {
return this.level_;
}
if (this.parent_) {
return this.parent_.getEffectiveLevel();
}
goog.asserts.fail('Root logger has no level set.');
return null;
};
/**
* Checks if a message of the given level would actually be logged by this
* logger. This check is based on the Loggers effective level, which may be
* inherited from its parent.
* @param {goog.debug.Logger.Level} level The level to check.
* @return {boolean} Whether the message would be logged.
*/
goog.debug.Logger.prototype.isLoggable = function(level) {
return goog.debug.LOGGING_ENABLED &&
level.value >= this.getEffectiveLevel().value;
};
/**
* Logs a message. If the logger is currently enabled for the
* given message level then the given message is forwarded to all the
* registered output Handler objects.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error|Object=} opt_exception An exception associated with the
* message.
*/
goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
// java caches the effective level, not sure it's necessary here
if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
// Message callbacks can be useful when a log message is expensive to build.
if (goog.isFunction(msg)) {
msg = msg();
}
this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
}
};
/**
* Creates a new log record and adds the exception (if present) to it.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {Error|Object=} opt_exception An exception associated with the
* message.
* @return {!goog.debug.LogRecord} A log record.
* @suppress {es5Strict}
*/
goog.debug.Logger.prototype.getLogRecord = function(level, msg, opt_exception) {
if (goog.debug.LogBuffer.isBufferingEnabled()) {
var logRecord =
goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
} else {
logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
}
if (opt_exception) {
logRecord.setException(opt_exception);
}
return logRecord;
};
/**
* Logs a message at the Logger.Level.SHOUT level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.SEVERE level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.WARNING level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.INFO level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.info = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.CONFIG level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.config = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINE level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINER level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
}
};
/**
* Logs a message at the Logger.Level.FINEST level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
if (goog.debug.LOGGING_ENABLED) {
this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
}
};
/**
* Logs a LogRecord. If the logger is currently enabled for the
* given message level then the given message is forwarded to all the
* registered output Handler objects.
* @param {goog.debug.LogRecord} logRecord A log record to log.
*/
goog.debug.Logger.prototype.logRecord = function(logRecord) {
if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
this.doLogRecord_(logRecord);
}
};
/**
* Logs a LogRecord.
* @param {goog.debug.LogRecord} logRecord A log record to log.
* @private
*/
goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
if (goog.debug.Logger.ENABLE_HIERARCHY) {
var target = this;
while (target) {
target.callPublish_(logRecord);
target = target.getParent();
}
} else {
for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++];) {
handler(logRecord);
}
}
};
/**
* Calls the handlers for publish.
* @param {goog.debug.LogRecord} logRecord The log record to publish.
* @private
*/
goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
if (this.handlers_) {
for (var i = 0, handler; handler = this.handlers_[i]; i++) {
handler(logRecord);
}
}
};
/**
* Sets the parent of this logger. This is used for setting up the logger tree.
* @param {goog.debug.Logger} parent The parent logger.
* @private
*/
goog.debug.Logger.prototype.setParent_ = function(parent) {
this.parent_ = parent;
};
/**
* Adds a child to this logger. This is used for setting up the logger tree.
* @param {string} name The leaf name of the child.
* @param {goog.debug.Logger} logger The child logger.
* @private
*/
goog.debug.Logger.prototype.addChild_ = function(name, logger) {
this.getChildren()[name] = logger;
};
/**
* There is a single global LogManager object that is used to maintain a set of
* shared state about Loggers and log services. This is loosely based on the
* java class java.util.logging.LogManager.
* @const
*/
goog.debug.LogManager = {};
/**
* Map of logger names to logger objects.
*
* @type {!Object<string, !goog.debug.Logger>}
* @private
*/
goog.debug.LogManager.loggers_ = {};
/**
* The root logger which is the root of the logger tree.
* @type {goog.debug.Logger}
* @private
*/
goog.debug.LogManager.rootLogger_ = null;
/**
* Initializes the LogManager if not already initialized.
*/
goog.debug.LogManager.initialize = function() {
if (!goog.debug.LogManager.rootLogger_) {
goog.debug.LogManager.rootLogger_ =
new goog.debug.Logger(goog.debug.Logger.ROOT_LOGGER_NAME);
goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
goog.debug.LogManager.rootLogger_;
goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
}
};
/**
* Returns all the loggers.
* @return {!Object<string, !goog.debug.Logger>} Map of logger names to logger
* objects.
*/
goog.debug.LogManager.getLoggers = function() {
return goog.debug.LogManager.loggers_;
};
/**
* Returns the root of the logger tree namespace, the logger with the empty
* string as its name.
*
* @return {!goog.debug.Logger} The root logger.
*/
goog.debug.LogManager.getRoot = function() {
goog.debug.LogManager.initialize();
return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
};
/**
* Finds a named logger.
*
* @param {string} name A name for the logger. This should be a dot-separated
* name and should normally be based on the package name or class name of the
* subsystem, such as goog.net.BrowserChannel.
* @return {!goog.debug.Logger} The named logger.
*/
goog.debug.LogManager.getLogger = function(name) {
goog.debug.LogManager.initialize();
var ret = goog.debug.LogManager.loggers_[name];
return ret || goog.debug.LogManager.createLogger_(name);
};
/**
* Creates a function that can be passed to goog.debug.catchErrors. The function
* will log all reported errors using the given logger.
* @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
* Defaults to the root logger.
* @return {function(Object)} The created function.
*/
goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
return function(info) {
var logger = opt_logger || goog.debug.LogManager.getRoot();
logger.severe(
'Error: ' + info.message + ' (' + info.fileName + ' @ Line: ' +
info.line + ')');
};
};
/**
* Creates the named logger. Will also create the parents of the named logger
* if they don't yet exist.
* @param {string} name The name of the logger.
* @return {!goog.debug.Logger} The named logger.
* @private
*/
goog.debug.LogManager.createLogger_ = function(name) {
// find parent logger
var logger = new goog.debug.Logger(name);
if (goog.debug.Logger.ENABLE_HIERARCHY) {
var lastDotIndex = name.lastIndexOf('.');
var parentName = name.substr(0, lastDotIndex);
var leafName = name.substr(lastDotIndex + 1);
var parentLogger = goog.debug.LogManager.getLogger(parentName);
// tell the parent about the child and the child about the parent
parentLogger.addChild_(leafName, logger);
logger.setParent_(parentLogger);
}
goog.debug.LogManager.loggers_[name] = logger;
return logger;
};

View file

@ -0,0 +1,242 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the LogRecord class. Please minimize
* dependencies this file has on other closure classes as any dependency it
* takes won't be able to use the logging infrastructure.
*
*/
goog.provide('goog.debug.LogRecord');
/**
* LogRecord objects are used to pass logging requests between
* the logging framework and individual log Handlers.
* @constructor
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @param {number=} opt_time Time this log record was created if other than now.
* If 0, we use #goog.now.
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
* should only be passed in when restoring a log record from persistence.
*/
goog.debug.LogRecord = function(
level, msg, loggerName, opt_time, opt_sequenceNumber) {
this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
};
/**
* Time the LogRecord was created.
* @type {number}
* @private
*/
goog.debug.LogRecord.prototype.time_;
/**
* Level of the LogRecord
* @type {goog.debug.Logger.Level}
* @private
*/
goog.debug.LogRecord.prototype.level_;
/**
* Message associated with the record
* @type {string}
* @private
*/
goog.debug.LogRecord.prototype.msg_;
/**
* Name of the logger that created the record.
* @type {string}
* @private
*/
goog.debug.LogRecord.prototype.loggerName_;
/**
* Sequence number for the LogRecord. Each record has a unique sequence number
* that is greater than all log records created before it.
* @type {number}
* @private
*/
goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
/**
* Exception associated with the record
* @type {Object}
* @private
*/
goog.debug.LogRecord.prototype.exception_ = null;
/**
* @define {boolean} Whether to enable log sequence numbers.
*/
goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
/**
* A sequence counter for assigning increasing sequence numbers to LogRecord
* objects.
* @type {number}
* @private
*/
goog.debug.LogRecord.nextSequenceNumber_ = 0;
/**
* Sets all fields of the log record.
* @param {goog.debug.Logger.Level} level One of the level identifiers.
* @param {string} msg The string message.
* @param {string} loggerName The name of the source logger.
* @param {number=} opt_time Time this log record was created if other than now.
* If 0, we use #goog.now.
* @param {number=} opt_sequenceNumber Sequence number of this log record. This
* should only be passed in when restoring a log record from persistence.
*/
goog.debug.LogRecord.prototype.reset = function(
level, msg, loggerName, opt_time, opt_sequenceNumber) {
if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
opt_sequenceNumber :
goog.debug.LogRecord.nextSequenceNumber_++;
}
this.time_ = opt_time || goog.now();
this.level_ = level;
this.msg_ = msg;
this.loggerName_ = loggerName;
delete this.exception_;
};
/**
* Get the source Logger's name.
*
* @return {string} source logger name (may be null).
*/
goog.debug.LogRecord.prototype.getLoggerName = function() {
return this.loggerName_;
};
/**
* Get the exception that is part of the log record.
*
* @return {Object} the exception.
*/
goog.debug.LogRecord.prototype.getException = function() {
return this.exception_;
};
/**
* Set the exception that is part of the log record.
*
* @param {Object} exception the exception.
*/
goog.debug.LogRecord.prototype.setException = function(exception) {
this.exception_ = exception;
};
/**
* Get the source Logger's name.
*
* @param {string} loggerName source logger name (may be null).
*/
goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
this.loggerName_ = loggerName;
};
/**
* Get the logging message level, for example Level.SEVERE.
* @return {goog.debug.Logger.Level} the logging message level.
*/
goog.debug.LogRecord.prototype.getLevel = function() {
return this.level_;
};
/**
* Set the logging message level, for example Level.SEVERE.
* @param {goog.debug.Logger.Level} level the logging message level.
*/
goog.debug.LogRecord.prototype.setLevel = function(level) {
this.level_ = level;
};
/**
* Get the "raw" log message, before localization or formatting.
*
* @return {string} the raw message string.
*/
goog.debug.LogRecord.prototype.getMessage = function() {
return this.msg_;
};
/**
* Set the "raw" log message, before localization or formatting.
*
* @param {string} msg the raw message string.
*/
goog.debug.LogRecord.prototype.setMessage = function(msg) {
this.msg_ = msg;
};
/**
* Get event time in milliseconds since 1970.
*
* @return {number} event time in millis since 1970.
*/
goog.debug.LogRecord.prototype.getMillis = function() {
return this.time_;
};
/**
* Set event time in milliseconds since 1970.
*
* @param {number} time event time in millis since 1970.
*/
goog.debug.LogRecord.prototype.setMillis = function(time) {
this.time_ = time;
};
/**
* Get the sequence number.
* <p>
* Sequence numbers are normally assigned in the LogRecord
* constructor, which assigns unique sequence numbers to
* each new LogRecord in increasing order.
* @return {number} the sequence number.
*/
goog.debug.LogRecord.prototype.getSequenceNumber = function() {
return this.sequenceNumber_;
};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,305 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implements the disposable interface. The dispose method is used
* to clean up references and resources.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.Disposable');
goog.provide('goog.dispose');
goog.provide('goog.disposeAll');
goog.require('goog.disposable.IDisposable');
/**
* Class that provides the basic implementation for disposable objects. If your
* class holds one or more references to COM objects, DOM nodes, or other
* disposable objects, it should extend this class or implement the disposable
* interface (defined in goog.disposable.IDisposable).
* @constructor
* @implements {goog.disposable.IDisposable}
*/
goog.Disposable = function() {
/**
* 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 {@code 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
* {@code 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.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.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
/**
* Maps the unique ID of every undisposed {@code goog.Disposable} object to
* the object itself.
* @type {!Object<number, !goog.Disposable>}
* @private
*/
goog.Disposable.instances_ = {};
/**
* @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
* haven't been disposed of.
*/
goog.Disposable.getUndisposedObjects = function() {
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() {
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() {
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 {@code goog.Disposable} should
* override {@link #disposeInternal} in order to delete references to COM
* objects, DOM nodes, and other disposable objects. Reentrant.
*
* @return {void} Nothing.
* @override
*/
goog.Disposable.prototype.dispose = function() {
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 Error(
this + ' did not call the goog.Disposable base ' +
'constructor or was disposed of after a clearUndisposedObjects ' +
'call');
}
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) {
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) {
if (this.disposed_) {
goog.isDef(opt_scope) ? callback.call(opt_scope) : callback();
return;
}
if (!this.onDisposeCallbacks_) {
this.onDisposeCallbacks_ = [];
}
this.onDisposeCallbacks_.push(
goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
};
/**
* Deletes or nulls out any references to COM objects, DOM nodes, or other
* disposable objects. Classes that extend {@code goog.Disposable} should
* override this method.
* Not reentrant. To avoid calling it twice, it must only be called from the
* subclass' {@code disposeInternal} method. Everywhere else the public
* {@code 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() {
if (this.onDisposeCallbacks_) {
while (this.onDisposeCallbacks_.length) {
this.onDisposeCallbacks_.shift()();
}
}
};
/**
* Returns True if we can verify the object is disposed.
* Calls {@code 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) {
if (obj && typeof obj.isDisposed == 'function') {
return obj.isDisposed();
}
return false;
};
/**
* Calls {@code 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.
*/
goog.dispose = function(obj) {
if (obj && typeof obj.dispose == 'function') {
obj.dispose();
}
};
/**
* Calls {@code dispose} on each member of the list that supports it. (If the
* member is an ArrayLike, then {@code goog.disposeAll()} will be called
* recursively on each of its members.) If the member is not an object with a
* {@code dispose()} method, then it is ignored.
* @param {...*} var_args The list.
*/
goog.disposeAll = function(var_args) {
for (var i = 0, len = arguments.length; i < len; ++i) {
var disposable = arguments[i];
if (goog.isArrayLike(disposable)) {
goog.disposeAll.apply(null, disposable);
} else {
goog.dispose(disposable);
}
}
};

View file

@ -0,0 +1,45 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the disposable interface. A disposable object
* has a dispose method to to clean up references and resources.
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.disposable.IDisposable');
/**
* Interface for a disposable object. If a instance requires cleanup
* (references COM objects, DOM nodes, or other disposable objects), it should
* implement this interface (it may subclass goog.Disposable).
* @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;

View file

@ -0,0 +1,311 @@
// Copyright 2017 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.Location != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o && (o instanceof win.Location || !(o instanceof win.Element)),
'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 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}
*/
goog.dom.asserts.assertIsHTMLAnchorElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLAnchorElement != 'undefined' &&
typeof win.Location != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLAnchorElement ||
!((o instanceof win.Location) || (o instanceof win.Element))),
'Argument is not a HTMLAnchorElement (or a non-Element mock); ' +
'got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLAnchorElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLLinkElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLLinkElement != 'undefined' &&
typeof win.Location != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLLinkElement ||
!((o instanceof win.Location) || (o instanceof win.Element))),
'Argument is not a HTMLLinkElement (or a non-Element mock); got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLLinkElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLImageElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLImageElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLImageElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLImageElement (or a non-Element mock); got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLImageElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLEmbedElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLEmbedElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLEmbedElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLEmbedElement (or a non-Element mock); got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLEmbedElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLFrameElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLFrameElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLFrameElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLFrameElement (or a non-Element mock); got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLFrameElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLIFrameElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLIFrameElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLIFrameElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLIFrameElement (or a non-Element mock); ' +
'got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLIFrameElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLObjectElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLObjectElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLObjectElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLObjectElement (or a non-Element mock); ' +
'got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLObjectElement} */ (o);
};
/**
* 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}
*/
goog.dom.asserts.assertIsHTMLScriptElement = function(o) {
if (goog.asserts.ENABLE_ASSERTS) {
var win = goog.dom.asserts.getWindow_(o);
if (typeof win.HTMLScriptElement != 'undefined' &&
typeof win.Element != 'undefined') {
goog.asserts.assert(
o &&
(o instanceof win.HTMLScriptElement ||
!(o instanceof win.Element)),
'Argument is not a HTMLScriptElement (or a non-Element mock); ' +
'got: %s',
goog.dom.asserts.debugStringForType_(o));
}
}
return /** @type {!HTMLScriptElement} */ (o);
};
/**
* 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) {
if (goog.isObject(value)) {
return value.constructor.displayName || value.constructor.name ||
Object.prototype.toString.call(value);
} else {
return value === undefined ? 'undefined' :
value === null ? 'null' : typeof value;
}
};
/**
* Gets window of element.
* @param {?Object} o
* @return {!Window}
* @private
*/
goog.dom.asserts.getWindow_ = function(o) {
var doc = o && o.ownerDocument;
var win = doc && /** @type {?Window} */ (doc.defaultView || doc.parentWindow);
return win || /** @type {!Window} */ (goog.global);
};

View file

@ -0,0 +1,73 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the dom package.
*
*/
goog.provide('goog.dom.BrowserFeature');
goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.dom.BrowserFeature = {
/**
* Whether attributes 'name' and 'type' can be added to an element after it's
* created. False in Internet Explorer prior to version 9.
*/
CAN_ADD_NAME_OR_TYPE_ATTRIBUTES:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* 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.)
*/
CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
/**
* Opera, Safari 3, and Internet Explorer 9 all support innerText but they
* include text nodes in script and style tags. Not document-mode-dependent.
*/
CAN_USE_INNER_TEXT:
(goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
/**
* MSIE, Opera, and Safari>=4 support element.parentElement to access an
* element's parent if it is an Element.
*/
CAN_USE_PARENT_ELEMENT_PROPERTY:
goog.userAgent.IE || goog.userAgent.OPERA || 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
*/
INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE,
/**
* Whether we use legacy IE range API.
*/
LEGACY_IE_RANGES:
goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
// Copyright 2017 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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() {};

View file

@ -0,0 +1,48 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of 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
};

View file

@ -0,0 +1,458 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.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.string');
goog.require('goog.string.Const');
/** @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) {
node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(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
};
/**
* Assigns known-safe HTML to an element's innerHTML property.
* @param {!Element} 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) {
if (goog.asserts.ENABLE_ASSERTS) {
var tagName = elem.tagName.toUpperCase();
if (goog.dom.safe.SET_INNER_HTML_DISALLOWED_TAGS_[tagName]) {
throw Error(
'goog.dom.safe.setInnerHtml cannot be used to set content of ' +
elem.tagName + '.');
}
}
elem.innerHTML = goog.html.SafeHtml.unwrap(html);
};
/**
* 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) {
elem.outerHTML = goog.html.SafeHtml.unwrap(html);
};
/**
* Sets the given element's style property to the contents of the provided
* SafeStyle object.
* @param {!Element} elem
* @param {!goog.html.SafeStyle} style
*/
goog.dom.safe.setStyle = function(elem, style) {
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.
*/
goog.dom.safe.documentWrite = function(doc, html) {
doc.write(goog.html.SafeHtml.unwrap(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.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setAnchorHref = function(anchor, url) {
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.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setImageSrc = function(imageElement, url) {
goog.dom.asserts.assertIsHTMLImageElement(imageElement);
/** @type {!goog.html.SafeUrl} */
var safeUrl;
if (url instanceof goog.html.SafeUrl) {
safeUrl = url;
} else {
safeUrl = goog.html.SafeUrl.sanitizeAssertUnchanged(url);
}
imageElement.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) {
goog.dom.asserts.assertIsHTMLEmbedElement(embed);
embed.src = goog.html.TrustedResourceUrl.unwrap(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.
*/
goog.dom.safe.setFrameSrc = function(frame, url) {
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.
*/
goog.dom.safe.setIframeSrc = function(iframe, url) {
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.
*/
goog.dom.safe.setIframeSrcdoc = function(iframe, html) {
goog.dom.asserts.assertIsHTMLIFrameElement(iframe);
iframe.srcdoc = goog.html.SafeHtml.unwrap(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.
* @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) {
goog.dom.asserts.assertIsHTMLLinkElement(link);
link.rel = rel;
if (goog.string.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);
} 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.sanitizeAssertUnchanged(url).getTypedStringValue();
}
};
/**
* 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.
*/
goog.dom.safe.setObjectData = function(object, url) {
goog.dom.asserts.assertIsHTMLObjectElement(object);
object.data = goog.html.TrustedResourceUrl.unwrap(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.
*/
goog.dom.safe.setScriptSrc = function(script, url) {
goog.dom.asserts.assertIsHTMLScriptElement(script);
script.src = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* 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.
*/
goog.dom.safe.setScriptContent = function(script, content) {
goog.dom.asserts.assertIsHTMLScriptElement(script);
script.text = goog.html.SafeScript.unwrap(content);
};
/**
* 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.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setLocationHref = function(loc, url) {
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 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 redirectUrl 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=} opt_name Name of the window to open in. Can be
* _top, etc as allowed by window.open().
* @param {string=} opt_specs Comma-separated list of specifications, same as
* in window.open().
* @param {boolean=} opt_replace Whether to replace the current entry in browser
* history, 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, opt_replace) {
/** @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 || window;
return win.open(
goog.html.SafeUrl.unwrap(safeUrl),
// 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 .
opt_name ? goog.string.Const.unwrap(opt_name) : '', opt_specs,
opt_replace);
};

View file

@ -0,0 +1,562 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines the goog.dom.TagName class. Its constants enumerate
* all HTML tag names specified in either the the W3C HTML 4.01 index of
* elements or the HTML5 draft specification.
*
* References:
* http://www.w3.org/TR/html401/index/elements.html
* http://dev.w3.org/html5/spec/section-index.html
*/
goog.provide('goog.dom.TagName');
goog.require('goog.dom.HtmlElement');
/**
* A tag name with the type of the element stored in the generic.
* @param {string} tagName
* @constructor
* @template T
*/
goog.dom.TagName = function(tagName) {
/** @private {string} */
this.tagName_ = tagName;
};
/**
* Returns the tag name.
* @return {string}
* @override
*/
goog.dom.TagName.prototype.toString = function() {
return this.tagName_;
};
// Closure Compiler unconditionally converts the following constants to their
// string value (goog.dom.TagName.A -> 'A'). These are the consequences:
// 1. Don't add any members or static members to goog.dom.TagName as they
// couldn't be accessed after this optimization.
// 2. Keep the constant name and its string value the same:
// goog.dom.TagName.X = new goog.dom.TagName('Y');
// is converted to 'X', not 'Y'.
/** @type {!goog.dom.TagName<!HTMLAnchorElement>} */
goog.dom.TagName.A = new goog.dom.TagName('A');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.ABBR = new goog.dom.TagName('ABBR');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.ACRONYM = new goog.dom.TagName('ACRONYM');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.ADDRESS = new goog.dom.TagName('ADDRESS');
/** @type {!goog.dom.TagName<!HTMLAppletElement>} */
goog.dom.TagName.APPLET = new goog.dom.TagName('APPLET');
/** @type {!goog.dom.TagName<!HTMLAreaElement>} */
goog.dom.TagName.AREA = new goog.dom.TagName('AREA');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.ARTICLE = new goog.dom.TagName('ARTICLE');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.ASIDE = new goog.dom.TagName('ASIDE');
/** @type {!goog.dom.TagName<!HTMLAudioElement>} */
goog.dom.TagName.AUDIO = new goog.dom.TagName('AUDIO');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.B = new goog.dom.TagName('B');
/** @type {!goog.dom.TagName<!HTMLBaseElement>} */
goog.dom.TagName.BASE = new goog.dom.TagName('BASE');
/** @type {!goog.dom.TagName<!HTMLBaseFontElement>} */
goog.dom.TagName.BASEFONT = new goog.dom.TagName('BASEFONT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.BDI = new goog.dom.TagName('BDI');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.BDO = new goog.dom.TagName('BDO');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.BIG = new goog.dom.TagName('BIG');
/** @type {!goog.dom.TagName<!HTMLQuoteElement>} */
goog.dom.TagName.BLOCKQUOTE = new goog.dom.TagName('BLOCKQUOTE');
/** @type {!goog.dom.TagName<!HTMLBodyElement>} */
goog.dom.TagName.BODY = new goog.dom.TagName('BODY');
/** @type {!goog.dom.TagName<!HTMLBRElement>} */
goog.dom.TagName.BR = new goog.dom.TagName('BR');
/** @type {!goog.dom.TagName<!HTMLButtonElement>} */
goog.dom.TagName.BUTTON = new goog.dom.TagName('BUTTON');
/** @type {!goog.dom.TagName<!HTMLCanvasElement>} */
goog.dom.TagName.CANVAS = new goog.dom.TagName('CANVAS');
/** @type {!goog.dom.TagName<!HTMLTableCaptionElement>} */
goog.dom.TagName.CAPTION = new goog.dom.TagName('CAPTION');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.CENTER = new goog.dom.TagName('CENTER');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.CITE = new goog.dom.TagName('CITE');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.CODE = new goog.dom.TagName('CODE');
/** @type {!goog.dom.TagName<!HTMLTableColElement>} */
goog.dom.TagName.COL = new goog.dom.TagName('COL');
/** @type {!goog.dom.TagName<!HTMLTableColElement>} */
goog.dom.TagName.COLGROUP = new goog.dom.TagName('COLGROUP');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.COMMAND = new goog.dom.TagName('COMMAND');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.DATA = new goog.dom.TagName('DATA');
/** @type {!goog.dom.TagName<!HTMLDataListElement>} */
goog.dom.TagName.DATALIST = new goog.dom.TagName('DATALIST');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.DD = new goog.dom.TagName('DD');
/** @type {!goog.dom.TagName<!HTMLModElement>} */
goog.dom.TagName.DEL = new goog.dom.TagName('DEL');
/** @type {!goog.dom.TagName<!HTMLDetailsElement>} */
goog.dom.TagName.DETAILS = new goog.dom.TagName('DETAILS');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.DFN = new goog.dom.TagName('DFN');
/** @type {!goog.dom.TagName<!HTMLDialogElement>} */
goog.dom.TagName.DIALOG = new goog.dom.TagName('DIALOG');
/** @type {!goog.dom.TagName<!HTMLDirectoryElement>} */
goog.dom.TagName.DIR = new goog.dom.TagName('DIR');
/** @type {!goog.dom.TagName<!HTMLDivElement>} */
goog.dom.TagName.DIV = new goog.dom.TagName('DIV');
/** @type {!goog.dom.TagName<!HTMLDListElement>} */
goog.dom.TagName.DL = new goog.dom.TagName('DL');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.DT = new goog.dom.TagName('DT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.EM = new goog.dom.TagName('EM');
/** @type {!goog.dom.TagName<!HTMLEmbedElement>} */
goog.dom.TagName.EMBED = new goog.dom.TagName('EMBED');
/** @type {!goog.dom.TagName<!HTMLFieldSetElement>} */
goog.dom.TagName.FIELDSET = new goog.dom.TagName('FIELDSET');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.FIGCAPTION = new goog.dom.TagName('FIGCAPTION');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.FIGURE = new goog.dom.TagName('FIGURE');
/** @type {!goog.dom.TagName<!HTMLFontElement>} */
goog.dom.TagName.FONT = new goog.dom.TagName('FONT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.FOOTER = new goog.dom.TagName('FOOTER');
/** @type {!goog.dom.TagName<!HTMLFormElement>} */
goog.dom.TagName.FORM = new goog.dom.TagName('FORM');
/** @type {!goog.dom.TagName<!HTMLFrameElement>} */
goog.dom.TagName.FRAME = new goog.dom.TagName('FRAME');
/** @type {!goog.dom.TagName<!HTMLFrameSetElement>} */
goog.dom.TagName.FRAMESET = new goog.dom.TagName('FRAMESET');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H1 = new goog.dom.TagName('H1');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H2 = new goog.dom.TagName('H2');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H3 = new goog.dom.TagName('H3');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H4 = new goog.dom.TagName('H4');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H5 = new goog.dom.TagName('H5');
/** @type {!goog.dom.TagName<!HTMLHeadingElement>} */
goog.dom.TagName.H6 = new goog.dom.TagName('H6');
/** @type {!goog.dom.TagName<!HTMLHeadElement>} */
goog.dom.TagName.HEAD = new goog.dom.TagName('HEAD');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.HEADER = new goog.dom.TagName('HEADER');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.HGROUP = new goog.dom.TagName('HGROUP');
/** @type {!goog.dom.TagName<!HTMLHRElement>} */
goog.dom.TagName.HR = new goog.dom.TagName('HR');
/** @type {!goog.dom.TagName<!HTMLHtmlElement>} */
goog.dom.TagName.HTML = new goog.dom.TagName('HTML');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.I = new goog.dom.TagName('I');
/** @type {!goog.dom.TagName<!HTMLIFrameElement>} */
goog.dom.TagName.IFRAME = new goog.dom.TagName('IFRAME');
/** @type {!goog.dom.TagName<!HTMLImageElement>} */
goog.dom.TagName.IMG = new goog.dom.TagName('IMG');
/** @type {!goog.dom.TagName<!HTMLInputElement>} */
goog.dom.TagName.INPUT = new goog.dom.TagName('INPUT');
/** @type {!goog.dom.TagName<!HTMLModElement>} */
goog.dom.TagName.INS = new goog.dom.TagName('INS');
/** @type {!goog.dom.TagName<!HTMLIsIndexElement>} */
goog.dom.TagName.ISINDEX = new goog.dom.TagName('ISINDEX');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.KBD = new goog.dom.TagName('KBD');
// HTMLKeygenElement is deprecated.
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.KEYGEN = new goog.dom.TagName('KEYGEN');
/** @type {!goog.dom.TagName<!HTMLLabelElement>} */
goog.dom.TagName.LABEL = new goog.dom.TagName('LABEL');
/** @type {!goog.dom.TagName<!HTMLLegendElement>} */
goog.dom.TagName.LEGEND = new goog.dom.TagName('LEGEND');
/** @type {!goog.dom.TagName<!HTMLLIElement>} */
goog.dom.TagName.LI = new goog.dom.TagName('LI');
/** @type {!goog.dom.TagName<!HTMLLinkElement>} */
goog.dom.TagName.LINK = new goog.dom.TagName('LINK');
/** @type {!goog.dom.TagName<!HTMLMapElement>} */
goog.dom.TagName.MAP = new goog.dom.TagName('MAP');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.MARK = new goog.dom.TagName('MARK');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.MATH = new goog.dom.TagName('MATH');
/** @type {!goog.dom.TagName<!HTMLMenuElement>} */
goog.dom.TagName.MENU = new goog.dom.TagName('MENU');
/** @type {!goog.dom.TagName<!HTMLMetaElement>} */
goog.dom.TagName.META = new goog.dom.TagName('META');
/** @type {!goog.dom.TagName<!HTMLMeterElement>} */
goog.dom.TagName.METER = new goog.dom.TagName('METER');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.NAV = new goog.dom.TagName('NAV');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.NOFRAMES = new goog.dom.TagName('NOFRAMES');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.NOSCRIPT = new goog.dom.TagName('NOSCRIPT');
/** @type {!goog.dom.TagName<!HTMLObjectElement>} */
goog.dom.TagName.OBJECT = new goog.dom.TagName('OBJECT');
/** @type {!goog.dom.TagName<!HTMLOListElement>} */
goog.dom.TagName.OL = new goog.dom.TagName('OL');
/** @type {!goog.dom.TagName<!HTMLOptGroupElement>} */
goog.dom.TagName.OPTGROUP = new goog.dom.TagName('OPTGROUP');
/** @type {!goog.dom.TagName<!HTMLOptionElement>} */
goog.dom.TagName.OPTION = new goog.dom.TagName('OPTION');
/** @type {!goog.dom.TagName<!HTMLOutputElement>} */
goog.dom.TagName.OUTPUT = new goog.dom.TagName('OUTPUT');
/** @type {!goog.dom.TagName<!HTMLParagraphElement>} */
goog.dom.TagName.P = new goog.dom.TagName('P');
/** @type {!goog.dom.TagName<!HTMLParamElement>} */
goog.dom.TagName.PARAM = new goog.dom.TagName('PARAM');
/** @type {!goog.dom.TagName<!HTMLPreElement>} */
goog.dom.TagName.PRE = new goog.dom.TagName('PRE');
/** @type {!goog.dom.TagName<!HTMLProgressElement>} */
goog.dom.TagName.PROGRESS = new goog.dom.TagName('PROGRESS');
/** @type {!goog.dom.TagName<!HTMLQuoteElement>} */
goog.dom.TagName.Q = new goog.dom.TagName('Q');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.RP = new goog.dom.TagName('RP');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.RT = new goog.dom.TagName('RT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.RUBY = new goog.dom.TagName('RUBY');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.S = new goog.dom.TagName('S');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SAMP = new goog.dom.TagName('SAMP');
/** @type {!goog.dom.TagName<!HTMLScriptElement>} */
goog.dom.TagName.SCRIPT = new goog.dom.TagName('SCRIPT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SECTION = new goog.dom.TagName('SECTION');
/** @type {!goog.dom.TagName<!HTMLSelectElement>} */
goog.dom.TagName.SELECT = new goog.dom.TagName('SELECT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SMALL = new goog.dom.TagName('SMALL');
/** @type {!goog.dom.TagName<!HTMLSourceElement>} */
goog.dom.TagName.SOURCE = new goog.dom.TagName('SOURCE');
/** @type {!goog.dom.TagName<!HTMLSpanElement>} */
goog.dom.TagName.SPAN = new goog.dom.TagName('SPAN');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.STRIKE = new goog.dom.TagName('STRIKE');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.STRONG = new goog.dom.TagName('STRONG');
/** @type {!goog.dom.TagName<!HTMLStyleElement>} */
goog.dom.TagName.STYLE = new goog.dom.TagName('STYLE');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SUB = new goog.dom.TagName('SUB');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SUMMARY = new goog.dom.TagName('SUMMARY');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SUP = new goog.dom.TagName('SUP');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.SVG = new goog.dom.TagName('SVG');
/** @type {!goog.dom.TagName<!HTMLTableElement>} */
goog.dom.TagName.TABLE = new goog.dom.TagName('TABLE');
/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */
goog.dom.TagName.TBODY = new goog.dom.TagName('TBODY');
/** @type {!goog.dom.TagName<!HTMLTableCellElement>} */
goog.dom.TagName.TD = new goog.dom.TagName('TD');
/** @type {!goog.dom.TagName<!HTMLTemplateElement>} */
goog.dom.TagName.TEMPLATE = new goog.dom.TagName('TEMPLATE');
/** @type {!goog.dom.TagName<!HTMLTextAreaElement>} */
goog.dom.TagName.TEXTAREA = new goog.dom.TagName('TEXTAREA');
/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */
goog.dom.TagName.TFOOT = new goog.dom.TagName('TFOOT');
/** @type {!goog.dom.TagName<!HTMLTableCellElement>} */
goog.dom.TagName.TH = new goog.dom.TagName('TH');
/** @type {!goog.dom.TagName<!HTMLTableSectionElement>} */
goog.dom.TagName.THEAD = new goog.dom.TagName('THEAD');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.TIME = new goog.dom.TagName('TIME');
/** @type {!goog.dom.TagName<!HTMLTitleElement>} */
goog.dom.TagName.TITLE = new goog.dom.TagName('TITLE');
/** @type {!goog.dom.TagName<!HTMLTableRowElement>} */
goog.dom.TagName.TR = new goog.dom.TagName('TR');
/** @type {!goog.dom.TagName<!HTMLTrackElement>} */
goog.dom.TagName.TRACK = new goog.dom.TagName('TRACK');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.TT = new goog.dom.TagName('TT');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.U = new goog.dom.TagName('U');
/** @type {!goog.dom.TagName<!HTMLUListElement>} */
goog.dom.TagName.UL = new goog.dom.TagName('UL');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.VAR = new goog.dom.TagName('VAR');
/** @type {!goog.dom.TagName<!HTMLVideoElement>} */
goog.dom.TagName.VIDEO = new goog.dom.TagName('VIDEO');
/** @type {!goog.dom.TagName<!goog.dom.HtmlElement>} */
goog.dom.TagName.WBR = new goog.dom.TagName('WBR');

View file

@ -0,0 +1,41 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @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) {
return goog.dom.tags.VOID_TAGS_[tagName] === true;
};

View file

@ -0,0 +1,409 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A 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
* - 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>
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.events.BrowserEvent');
goog.provide('goog.events.BrowserEvent.MouseButton');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.reflect');
goog.require('goog.userAgent');
/**
* 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) {
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;
/**
* 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
};
/**
* Static data for mapping mouse buttons.
* @type {!Array<number>}
*/
goog.events.BrowserEvent.IEButtonMap = [
1, // LEFT
4, // MIDDLE
2 // RIGHT
];
/**
* 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) {
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[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;
}
}
// TODO(arv): Use goog.events.EventType when it has been refactored into its
// own file.
} else if (type == goog.events.EventType.MOUSEOVER) {
relatedTarget = e.fromElement;
} else if (type == goog.events.EventType.MOUSEOUT) {
relatedTarget = e.toElement;
}
this.relatedTarget = relatedTarget;
if (!goog.isNull(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 {
// 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.state = e.state;
this.event_ = e;
if (e.defaultPrevented) {
this.preventDefault();
}
};
/**
* 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) {
if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
if (this.type == 'click') {
return button == goog.events.BrowserEvent.MouseButton.LEFT;
} else {
return !!(
this.event_.button & goog.events.BrowserEvent.IEButtonMap[button]);
}
} else {
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() {
// Webkit does not ctrl+click to be a right-click, so we
// normalize it to behave like Gecko and Opera.
return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
!(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
};
/**
* @override
*/
goog.events.BrowserEvent.prototype.stopPropagation = function() {
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() {
goog.events.BrowserEvent.superClass_.preventDefault.call(this);
var be = this.event_;
if (!be.preventDefault) {
be.returnValue = false;
if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
try {
// Most keys can be prevented using returnValue. Some special keys
// require setting the keyCode to -1 as well:
//
// In IE7:
// F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
//
// In IE8:
// Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
//
// We therefore do this for all function keys as well as when Ctrl key
// is pressed.
var VK_F1 = 112;
var VK_F12 = 123;
if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
be.keyCode = -1;
}
} catch (ex) {
// IE throws an 'access denied' exception when trying to change
// keyCode in some situations (e.g. srcElement is input[type=file],
// or srcElement is an anchor tag rewritten by parent's innerHTML).
// Do nothing in this case.
}
}
} else {
be.preventDefault();
}
};
/**
* @return {Event} The underlying browser event object.
*/
goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
return this.event_;
};

View file

@ -0,0 +1,122 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the events package.
*
*/
goog.provide('goog.events.BrowserFeature');
goog.require('goog.userAgent');
goog.scope(function() {
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.events.BrowserFeature = {
/**
* Whether the button attribute of the event is W3C compliant. False in
* Internet Explorer prior to version 9; document-version dependent.
*/
HAS_W3C_BUTTON:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* Whether the browser supports full W3C event model.
*/
HAS_W3C_EVENT_SUPPORT:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* To prevent default in IE7-8 for certain keydown events we need set the
* keyCode to -1.
*/
SET_KEY_CODE_TO_PREVENT_DEFAULT:
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
/**
* Whether the {@code navigator.onLine} property is supported.
*/
HAS_NAVIGATOR_ONLINE_PROPERTY:
!goog.userAgent.WEBKIT || goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network online/offline events are supported.
*/
HAS_HTML5_NETWORK_EVENT_SUPPORT:
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network events fire on document.body, or otherwise the
* window.
*/
HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
/**
* 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']['msMaxTouchPoints'])),
/**
* 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;
}
});
goog.global.addEventListener('test', goog.nullFunction, options);
goog.global.removeEventListener('test', goog.nullFunction, options);
return passive;
})
};
/**
* 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
*/
function purify(fn) {
return ({valueOf: fn}).valueOf();
}
}); // goog.scope

View file

@ -0,0 +1,143 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A base class for event objects.
*
*/
goog.provide('goog.events.Event');
goog.provide('goog.events.EventLike');
/**
* 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 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;
/**
* A base class for event objects, so that they can support preventDefault and
* stopPropagation.
*
* @suppress {underscore} Several properties on this class are technically
* public, but referencing these properties outside this package is strongly
* discouraged.
*
* @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 {@code EventTarget} interface
* declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
* @constructor
*/
goog.events.Event = function(type, opt_target) {
/**
* 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}
* @public
*/
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 value for in internal capture/bubble processing for IE.
* @type {boolean}
* @public
*/
this.returnValue_ = true;
};
/**
* Stops event propagation.
*/
goog.events.Event.prototype.stopPropagation = function() {
this.propagationStopped_ = true;
};
/**
* Prevents the default action, for example a link redirecting to a url.
*/
goog.events.Event.prototype.preventDefault = function() {
this.defaultPrevented = true;
this.returnValue_ = false;
};
/**
* Stops the propagation of the event. It is equivalent to
* {@code 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.
*/
goog.events.Event.stopPropagation = function(e) {
e.stopPropagation();
};
/**
* Prevents the default action. It is equivalent to
* {@code 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.
*/
goog.events.Event.preventDefault = function(e) {
e.preventDefault();
};

View file

@ -0,0 +1,46 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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) {
/** @const */ this.id = eventId;
};
/**
* @override
*/
goog.events.EventId.prototype.toString = function() {
return this.id;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,394 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A disposable implementation of a custom
* listenable/event target. See also: documentation for
* {@code goog.events.Listenable}.
*
* @author arv@google.com (Erik Arvidsson) [Original implementation]
* @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');
/**
* An implementation of {@code 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
* {@code getParentEventTarget}. To set the parent, call
* {@code 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() {
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() {
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) {
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 {@code #listen} instead, when possible. Otherwise, use
* {@code 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) {
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} 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 {@code #unlisten} instead, when possible. Otherwise, use
* {@code 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) {
goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
};
/** @override */
goog.events.EventTarget.prototype.dispatchEvent = function(e) {
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
*/
goog.events.EventTarget.prototype.disposeInternal = function() {
goog.events.EventTarget.superClass_.disposeInternal.call(this);
this.removeAllListeners();
this.parentEventTarget_ = null;
};
/** @override */
goog.events.EventTarget.prototype.listen = function(
type, listener, opt_useCapture, opt_listenerScope) {
this.assertInitialized_();
return this.eventTargetListeners_.add(
String(type), listener, false /* callOnce */, opt_useCapture,
opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.listenOnce = function(
type, listener, opt_useCapture, opt_listenerScope) {
return this.eventTargetListeners_.add(
String(type), listener, true /* callOnce */, opt_useCapture,
opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.unlisten = function(
type, listener, opt_useCapture, opt_listenerScope) {
return this.eventTargetListeners_.remove(
String(type), listener, opt_useCapture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.unlistenByKey = function(key) {
return this.eventTargetListeners_.removeByKey(key);
};
/** @override */
goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
// 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);
};
/** @override */
goog.events.EventTarget.prototype.fireListeners = function(
type, capture, eventObject) {
// 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.returnValue_ != false;
};
/** @override */
goog.events.EventTarget.prototype.getListeners = function(type, capture) {
return this.eventTargetListeners_.getListeners(String(type), capture);
};
/** @override */
goog.events.EventTarget.prototype.getListener = function(
type, listener, capture, opt_listenerScope) {
return this.eventTargetListeners_.getListener(
String(type), listener, capture, opt_listenerScope);
};
/** @override */
goog.events.EventTarget.prototype.hasListener = function(
opt_type, opt_capture) {
var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
return this.eventTargetListeners_.hasListener(id, opt_capture);
};
/**
* Sets the target to be used for {@code event.target} when firing
* event. Mainly used for testing. For example, see
* {@code goog.testing.events.mixinListenable}.
* @param {!Object} target The target.
*/
goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
this.actualEventTarget_ = target;
};
/**
* Asserts that the event target instance is initialized properly.
* @private
*/
goog.events.EventTarget.prototype.assertInitialized_ = function() {
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) {
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 (goog.isString(e)) {
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.propagationStopped_ && 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.propagationStopped_) {
currentTarget = /** @type {?} */ (e.currentTarget = target);
rv = currentTarget.fireListeners(type, true, e) && rv;
if (!e.propagationStopped_) {
rv = currentTarget.fireListeners(type, false, e) && rv;
}
}
// Executes all bubble listeners on the ancestors, if any.
if (opt_ancestorsTree) {
for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
currentTarget = e.currentTarget = opt_ancestorsTree[i];
rv = currentTarget.fireListeners(type, false, e) && rv;
}
}
return rv;
};

View file

@ -0,0 +1,295 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Event Types.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.events.EventType');
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.
* @suppress {missingRequire|missingProvide}
* @private
*/
goog.events.getVendorPrefixedName_ = function(eventName) {
return goog.userAgent.WEBKIT ?
'webkit' + eventName :
(goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() :
eventName.toLowerCase());
};
/**
* Constants for event names.
* @enum {string}
*/
goog.events.EventType = {
// Mouse events
CLICK: 'click',
RIGHTCLICK: 'rightclick',
DBLCLICK: 'dblclick',
MOUSEDOWN: 'mousedown',
MOUSEUP: 'mouseup',
MOUSEOVER: 'mouseover',
MOUSEOUT: 'mouseout',
MOUSEMOVE: 'mousemove',
MOUSEENTER: 'mouseenter',
MOUSELEAVE: 'mouseleave',
// 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
// NOTE: The following two events are not stable in cross-browser usage.
// WebKit and Opera implement DOMFocusIn/Out.
// IE implements focusin/out.
// Gecko implements neither see bug at
// https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
// The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
// http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
// You can use FOCUS in Capture phase until implementations converge.
FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
// 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',
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',
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.
/** @suppress {missingRequire} */
ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
/** @suppress {missingRequire} */
ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
/** @suppress {missingRequire} */
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
/** @suppress {missingRequire} */
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 http://developer.chrome.com/dev/apps/webview_tag.html
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
// {@code 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'
};

View file

@ -0,0 +1,338 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An interface for a listenable JavaScript object.
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.events.Listenable');
goog.provide('goog.events.ListenableKey');
/** @suppress {extraRequire} */
goog.require('goog.events.EventId');
goog.forwardDeclare('goog.events.EventLike');
goog.forwardDeclare('goog.events.EventTarget');
/**
* 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 {@code #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 {@code #getParentEventTarget} return
* null).
*
* Implementation of this class can be used with or independently from
* goog.events.
*
* Implementation must call {@code #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, do 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) {
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) {
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;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* 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=} opt_type Type of event to remove, default is to
* remove all types.
* @return {number} Number of listeners removed.
*/
goog.events.Listenable.prototype.removeAllListeners;
/**
* 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 ({@code 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;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* 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;
/**
* 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() {
return ++goog.events.ListenableKey.counter_;
};
/**
* The source event target.
* @type {Object|goog.events.Listenable|goog.events.EventTarget}
*/
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;

View file

@ -0,0 +1,128 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Listener object.
* @see ../demos/events.html
*/
goog.provide('goog.events.Listener');
goog.require('goog.events.ListenableKey');
/**
* 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) {
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.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() {
this.removed = true;
this.listener = null;
this.proxy = null;
this.src = null;
this.handler = null;
};

View file

@ -0,0 +1,307 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A map of listeners that provides utility functions to
* deal with listeners on an event target. Used by
* {@code goog.events.EventTarget}.
*
* WARNING: Do not use this class from outside goog.events package.
*
* @visibility {//closure/goog/bin/sizetests:__pkg__}
* @visibility {//closure/goog:__pkg__}
* @visibility {//closure/goog/events:__pkg__}
* @visibility {//closure/goog/labs/events:__pkg__}
*/
goog.provide('goog.events.ListenerMap');
goog.require('goog.array');
goog.require('goog.events.Listener');
goog.require('goog.object');
/**
* Creates a new listener map.
* @param {EventTarget|goog.events.Listenable} src The src object.
* @constructor
* @final
*/
goog.events.ListenerMap = function(src) {
/** @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() {
return this.typeCount_;
};
/**
* @return {number} Total number of registered listeners.
*/
goog.events.ListenerMap.prototype.getListenerCount = function() {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
var hasType = goog.isDef(opt_type);
var typeStr = hasType ? opt_type.toString() : '';
var hasCapture = goog.isDef(opt_capture);
return goog.object.some(this.listeners, function(listenerArray, type) {
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) {
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;
};

View file

@ -0,0 +1,106 @@
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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 {!Blob} blob The object for which to create the URL.
* @return {string} The URL for the object.
*/
goog.fs.url.createObjectUrl = function(blob) {
return goog.fs.url.getUrlObject_().createObjectURL(blob);
};
/**
* 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.
*/
goog.fs.url.revokeObjectUrl = function(url) {
goog.fs.url.getUrlObject_().revokeObjectURL(url);
};
/**
* @typedef {{createObjectURL: (function(!Blob): string),
* revokeObjectURL: function(string): void}}
*/
goog.fs.url.UrlObject_;
/**
* 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() {
var urlObject = goog.fs.url.findUrlObject_();
if (urlObject != null) {
return urlObject;
} else {
throw 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() {
// This is what the spec says to do
// http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL
if (goog.isDef(goog.global.URL) &&
goog.isDef(goog.global.URL.createObjectURL)) {
return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL);
// This is what Chrome does (as of 10.0.648.6 dev)
} else if (
goog.isDef(goog.global.webkitURL) &&
goog.isDef(goog.global.webkitURL.createObjectURL)) {
return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL);
// This is what the spec used to say to do
} else if (goog.isDef(goog.global.createObjectURL)) {
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() {
return goog.fs.url.findUrlObject_() != null;
};

View file

@ -0,0 +1,483 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for creating functions. Loosely inspired by the
* java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
*
* @author nicksantos@google.com (Nick Santos)
*/
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) {
return function() { return retValue; };
};
/**
* Always returns false.
* @type {function(...): boolean}
*/
goog.functions.FALSE = goog.functions.constant(false);
/**
* Always returns true.
* @type {function(...): boolean}
*/
goog.functions.TRUE = goog.functions.constant(true);
/**
* Always returns NULL.
* @type {function(...): null}
*/
goog.functions.NULL = goog.functions.constant(null);
/**
* 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) {
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) {
return function() { throw 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) {
return function() { 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) {
opt_numArgs = opt_numArgs || 0;
return function() {
return f.apply(this, 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) {
return function() { 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) {
var rightArgs = Array.prototype.slice.call(arguments, 1);
return function() {
var newArgs = Array.prototype.slice.call(arguments);
newArgs.push.apply(newArgs, rightArgs);
return fn.apply(this, 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) {
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) {
return function(other) {
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) {
var functions = arguments;
var length = functions.length;
return function() {
var result;
if (length) {
result = functions[length - 1].apply(this, arguments);
}
for (var i = length - 2; i >= 0; i--) {
result = functions[i].call(this, 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) {
var functions = arguments;
var length = functions.length;
return function() {
var result;
for (var i = 0; i < length; i++) {
result = functions[i].apply(this, 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) {
var functions = arguments;
var length = functions.length;
return function() {
for (var i = 0; i < length; i++) {
if (!functions[i].apply(this, 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) {
var functions = arguments;
var length = functions.length;
return function() {
for (var i = 0; i < length; i++) {
if (functions[i].apply(this, 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) {
return function() { return !f.apply(this, 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 {@code constructor}.
* @template T
*/
goog.functions.create = function(constructor, var_args) {
/**
* @constructor
* @final
*/
var temp = function() {};
temp.prototype = constructor.prototype;
// obj will have constructor's prototype in its chain and
// 'obj instanceof constructor' will be true.
var 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.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) {
var called = false;
var value;
return function() {
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) {
// 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.
var inner = f;
return function() {
if (inner) {
var 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 {@code
* 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) {
var timeout = 0;
return /** @type {function(...?)} */ (function(var_args) {
goog.global.clearTimeout(timeout);
var args = arguments;
timeout = goog.global.setTimeout(function() {
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) {
var timeout = 0;
var shouldFire = false;
var args = [];
var handleTimeout = function() {
timeout = 0;
if (shouldFire) {
shouldFire = false;
fire();
}
};
var fire = function() {
timeout = goog.global.setTimeout(handleTimeout, interval);
f.apply(opt_scope, args);
};
return /** @type {function(...?)} */ (function(var_args) {
args = 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) {
var timeout = 0;
var handleTimeout = function() {
timeout = 0;
};
return /** @type {function(...?)} */ (function(var_args) {
if (!timeout) {
timeout = goog.global.setTimeout(handleTimeout, interval);
f.apply(opt_scope, arguments);
}
});
};

View file

@ -0,0 +1,195 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Transitional utilities to unsafely trust random strings as
* goog.html types. Intended for temporary use when upgrading a library that
* used to accept plain strings to use safe types, but where it's not
* practical to transitively update callers.
*
* IMPORTANT: No new code should use the conversion functions in this file,
* they are intended for refactoring old code to use goog.html types. New code
* should construct goog.html types via their APIs, template systems or
* sanitizers. If thats not possible it should use
* goog.html.uncheckedconversions and undergo security review.
*
* The semantics of the conversions in goog.html.legacyconversions are very
* different from the ones provided by goog.html.uncheckedconversions. The
* latter are for use in code where it has been established through manual
* security review that the value produced by a piece of code will always
* satisfy the SafeHtml contract (e.g., the output of a secure HTML sanitizer).
* In uses of goog.html.legacyconversions, this guarantee is not given -- the
* value in question originates in unreviewed legacy code and there is no
* guarantee that it satisfies the SafeHtml contract.
*
* There are only three valid uses of legacyconversions:
*
* 1. Introducing a goog.html version of a function which currently consumes
* string and passes that string to a DOM API which can execute script - and
* hence cause XSS - like innerHTML. For example, Dialog might expose a
* setContent method which takes a string and sets the innerHTML property of
* an element with it. In this case a setSafeHtmlContent function could be
* added, consuming goog.html.SafeHtml instead of string, and using
* goog.dom.safe.setInnerHtml instead of directly setting innerHTML.
* setContent could then internally use legacyconversions to create a SafeHtml
* from string and pass the SafeHtml to setSafeHtmlContent. In this scenario
* remember to document the use of legacyconversions in the modified setContent
* and consider deprecating it as well.
*
* 2. Automated refactoring of application code which handles HTML as string
* but needs to call a function which only takes goog.html types. For example,
* in the Dialog scenario from (1) an alternative option would be to refactor
* setContent to accept goog.html.SafeHtml instead of string and then refactor
* all current callers to use legacyconversions to pass SafeHtml. This is
* generally preferable to (1) because it keeps the library clean of
* legacyconversions, and makes code sites in application code that are
* potentially vulnerable to XSS more apparent.
*
* 3. Old code which needs to call APIs which consume goog.html types and for
* which it is prohibitively expensive to refactor to use goog.html types.
* Generally, this is code where safety from XSS is either hopeless or
* unimportant.
*
* @visibility {//closure/goog/html:approved_for_legacy_conversion}
* @visibility {//closure/goog/bin/sizetests:__pkg__}
*/
goog.provide('goog.html.legacyconversions');
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');
/**
* Performs an "unchecked conversion" from string to SafeHtml for legacy API
* purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} html A string to be converted to SafeHtml.
* @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
* object.
*/
goog.html.legacyconversions.safeHtmlFromString = function(html) {
goog.html.legacyconversions.reportCallback_();
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
html, null /* dir */);
};
/**
* Performs an "unchecked conversion" from string to SafeScript for legacy API
* purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} script A string to be converted to SafeScript.
* @return {!goog.html.SafeScript} The value of script, wrapped in a SafeScript
* object.
*/
goog.html.legacyconversions.safeScriptFromString = function(script) {
goog.html.legacyconversions.reportCallback_();
return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
script);
};
/**
* Performs an "unchecked conversion" from string to SafeStyle for legacy API
* purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} style A string to be converted to SafeStyle.
* @return {!goog.html.SafeStyle} The value of style, wrapped in a SafeStyle
* object.
*/
goog.html.legacyconversions.safeStyleFromString = function(style) {
goog.html.legacyconversions.reportCallback_();
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};
/**
* Performs an "unchecked conversion" from string to SafeStyleSheet for legacy
* API purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} styleSheet A string to be converted to SafeStyleSheet.
* @return {!goog.html.SafeStyleSheet} The value of style sheet, wrapped in
* a SafeStyleSheet object.
*/
goog.html.legacyconversions.safeStyleSheetFromString = function(styleSheet) {
goog.html.legacyconversions.reportCallback_();
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
};
/**
* Performs an "unchecked conversion" from string to SafeUrl for legacy API
* purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} url A string to be converted to SafeUrl.
* @return {!goog.html.SafeUrl} The value of url, wrapped in a SafeUrl
* object.
*/
goog.html.legacyconversions.safeUrlFromString = function(url) {
goog.html.legacyconversions.reportCallback_();
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Performs an "unchecked conversion" from string to TrustedResourceUrl for
* legacy API purposes.
*
* Please read fileoverview documentation before using.
*
* @param {string} url A string to be converted to TrustedResourceUrl.
* @return {!goog.html.TrustedResourceUrl} The value of url, wrapped in a
* TrustedResourceUrl object.
*/
goog.html.legacyconversions.trustedResourceUrlFromString = function(url) {
goog.html.legacyconversions.reportCallback_();
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* @private {function(): undefined}
*/
goog.html.legacyconversions.reportCallback_ = goog.nullFunction;
/**
* Sets a function that will be called every time a legacy conversion is
* performed. The function is called with no parameters but it can use
* goog.debug.getStacktrace to get a stacktrace.
*
* @param {function(): undefined} callback Error callback as defined above.
*/
goog.html.legacyconversions.setReportCallback = function(callback) {
goog.html.legacyconversions.reportCallback_ = callback;
};

View file

@ -0,0 +1,994 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeHtml type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeHtml');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom.TagName');
goog.require('goog.dom.tags');
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.i18n.bidi.Dir');
goog.require('goog.i18n.bidi.DirectionalString');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string that is safe to use in HTML context in DOM APIs and HTML documents.
*
* A SafeHtml 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 HTML in a browser.
*
* Values of this type are guaranteed to be safe to use in HTML contexts,
* such as, assignment to the innerHTML DOM property, or interpolation into
* a HTML template in HTML PC_DATA context, in the sense that the use will not
* result in a Cross-Site-Scripting vulnerability.
*
* Instances of this type must be created via the factory methods
* ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
* etc 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.
*
* @see goog.html.SafeHtml#create
* @see goog.html.SafeHtml#htmlEscape
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.SafeHtml = function() {
/**
* The contained value of this SafeHtml. The field has a purposely ugly
* name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeHtml#unwrap
* @const {!Object}
* @private
*/
this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
/**
* This SafeHtml's directionality, or null if unknown.
* @private {?goog.i18n.bidi.Dir}
*/
this.dir_ = null;
};
/**
* @override
* @const
*/
goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
/** @override */
goog.html.SafeHtml.prototype.getDirection = function() {
return this.dir_;
};
/**
* @override
* @const
*/
goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
/**
* Returns this SafeHtml's value as string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.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.SafeHtml#unwrap
* @override
*/
goog.html.SafeHtml.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeHtml, use
* {@code goog.html.SafeHtml.unwrap}.
*
* @see goog.html.SafeHtml#unwrap
* @override
*/
goog.html.SafeHtml.prototype.toString = function() {
return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
'}';
};
}
/**
* Performs a runtime check that the provided object is indeed a SafeHtml
* object, and returns its value.
* @param {!goog.html.SafeHtml} safeHtml The object to extract from.
* @return {string} The SafeHtml object's contained string, unless the run-time
* type check fails. In that case, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeHtml.unwrap = function(safeHtml) {
// Perform additional run-time type-checking to ensure that safeHtml 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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeHtml instanceof goog.html.SafeHtml &&
safeHtml.constructor === goog.html.SafeHtml &&
safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeHtml, got \'' +
safeHtml + '\' of type ' + goog.typeOf(safeHtml));
return 'type_error:SafeHtml';
}
};
/**
* Shorthand for union of types that can sensibly be converted to strings
* or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
* @private
* @typedef {string|number|boolean|!goog.string.TypedString|
* !goog.i18n.bidi.DirectionalString}
*/
goog.html.SafeHtml.TextOrHtml_;
/**
* Returns HTML-escaped text as a SafeHtml object.
*
* If text is of a type that implements
* {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
* {@code SafeHtml} object is set to {@code text}'s directionality, if known.
* Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
* {@code null}).
*
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var dir = null;
if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
dir = textOrHtml.getDirection();
}
var textAsString;
if (textOrHtml.implementsGoogStringTypedString) {
textAsString = textOrHtml.getTypedStringValue();
} else {
textAsString = String(textOrHtml);
}
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.htmlEscape(textAsString), dir);
};
/**
* Returns HTML-escaped text as a SafeHtml object, with newlines changed to
* &lt;br&gt;.
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
html.getDirection());
};
/**
* Returns HTML-escaped text as a SafeHtml object, with newlines changed to
* &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
* entity #160 is used to make it safer for XML.
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
html.getDirection());
};
/**
* Coerces an arbitrary object into a SafeHtml object.
*
* If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
* object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
* HTML-escaped. If {@code textOrHtml} is of a type that implements
* {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
* preserved.
*
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
* coerce.
* @return {!goog.html.SafeHtml} The resulting SafeHtml object.
* @deprecated Use goog.html.SafeHtml.htmlEscape.
*/
goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
/**
* @const
* @private
*/
goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
/**
* Set of attributes containing URL as defined at
* http://www.w3.org/TR/html5/index.html#attributes-1.
* @private @const {!Object<string,boolean>}
*/
goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet(
'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster',
'src');
/**
* Tags which are unsupported via create(). They might be supported via a
* tag-specific create method. These are tags which might require a
* TrustedResourceUrl in one of their attributes or a restricted type for
* their content.
* @private @const {!Object<string,boolean>}
*/
goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED,
goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH,
goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT,
goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE);
/**
* @typedef {string|number|goog.string.TypedString|
* goog.html.SafeStyle.PropertyMap|undefined}
*/
goog.html.SafeHtml.AttributeValue;
/**
* Creates a SafeHtml content consisting of a tag with optional attributes and
* optional content.
*
* For convenience tag names and attribute names are accepted as regular
* strings, instead of goog.string.Const. Nevertheless, you should not pass
* user-controlled values to these parameters. Note that these parameters are
* syntactically validated at runtime, and invalid values will result in
* an exception.
*
* Example usage:
*
* goog.html.SafeHtml.create('br');
* goog.html.SafeHtml.create('div', {'class': 'a'});
* goog.html.SafeHtml.create('p', {}, 'a');
* goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
*
* goog.html.SafeHtml.create('span', {
* 'style': {'margin': '0'}
* });
*
* To guarantee SafeHtml's type contract is upheld there are restrictions on
* attribute values and tag names.
*
* - For attributes which contain script code (on*), a goog.string.Const is
* required.
* - For attributes which contain style (style), a goog.html.SafeStyle or a
* goog.html.SafeStyle.PropertyMap is required.
* - For attributes which are interpreted as URLs (e.g. src, href) a
* goog.html.SafeUrl, goog.string.Const or string is required. If a string
* is passed, it will be sanitized with SafeUrl.sanitize().
* - For tags which can load code or set security relevant page metadata,
* more specific goog.html.SafeHtml.create*() functions must be used. Tags
* which are not supported by this function are applet, base, embed, iframe,
* link, math, object, script, style, svg, and template.
*
* @param {!goog.dom.TagName|string} tagName The name of the tag. Only tag names
* consisting of [a-zA-Z0-9-] are allowed. Tag names documented above are
* disallowed.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. This must be empty for void tags
* like <br>. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided.
* @throws {goog.asserts.AssertionError} If content for void tag is provided.
*/
goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
goog.html.SafeHtml.verifyTagName(String(tagName));
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
String(tagName), opt_attributes, opt_content);
};
/**
* Verifies if the tag name is valid and if it doesn't change the context.
* E.g. STRONG is fine but SCRIPT throws because it changes context. See
* goog.html.SafeHtml.create for an explanation of allowed tags.
* @param {string} tagName
* @throws {Error} If invalid tag name is provided.
* @package
*/
goog.html.SafeHtml.verifyTagName = function(tagName) {
if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
throw Error('Invalid tag name <' + tagName + '>.');
}
if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
}
};
/**
* Creates a SafeHtml representing an iframe tag.
*
* This by default restricts the iframe as much as possible by setting the
* sandbox attribute to the empty string. If the iframe requires less
* restrictions, set the sandbox attribute as tight as possible, but do not rely
* on the sandbox as a security feature because it is not supported by older
* browsers. If a sandbox is essential to security (e.g. for third-party
* frames), use createSandboxIframe which checks for browser support.
*
* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
*
* @param {?goog.html.TrustedResourceUrl=} opt_src The value of the src
* attribute. If null or undefined src will not be set.
* @param {?goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
* If null or undefined srcdoc will not be set.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided. If opt_attributes contains the src or srcdoc attributes.
*/
goog.html.SafeHtml.createIframe = function(
opt_src, opt_srcdoc, opt_attributes, opt_content) {
if (opt_src) {
// Check whether this is really TrustedResourceUrl.
goog.html.TrustedResourceUrl.unwrap(opt_src);
}
var fixedAttributes = {};
fixedAttributes['src'] = opt_src || null;
fixedAttributes['srcdoc'] =
opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc);
var defaultAttributes = {'sandbox': ''};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'iframe', attributes, opt_content);
};
/**
* Creates a SafeHtml representing a sandboxed iframe tag.
*
* The sandbox attribute is enforced in its most restrictive mode, an empty
* string. Consequently, the security requirements for the src and srcdoc
* attributes are relaxed compared to SafeHtml.createIframe. This function
* will throw on browsers that do not support the sandbox attribute, as
* determined by SafeHtml.canUseSandboxIframe.
*
* The SafeHtml returned by this function can trigger downloads with no
* user interaction on Chrome (though only a few, further attempts are blocked).
* Firefox and IE will block all downloads from the sandbox.
*
* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
* @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html
*
* @param {string|!goog.html.SafeUrl=} opt_src The value of the src
* attribute. If null or undefined src will not be set.
* @param {string=} opt_srcdoc The value of the srcdoc attribute.
* If null or undefined srcdoc will not be set. Will not be sanitized.
* @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided. If opt_attributes contains the src, srcdoc or sandbox
* attributes. If browser does not support the sandbox attribute on iframe.
*/
goog.html.SafeHtml.createSandboxIframe = function(
opt_src, opt_srcdoc, opt_attributes, opt_content) {
if (!goog.html.SafeHtml.canUseSandboxIframe()) {
throw new Error('The browser does not support sandboxed iframes.');
}
var fixedAttributes = {};
if (opt_src) {
// Note that sanitize is a no-op on SafeUrl.
fixedAttributes['src'] =
goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src));
} else {
fixedAttributes['src'] = null;
}
fixedAttributes['srcdoc'] = opt_srcdoc || null;
fixedAttributes['sandbox'] = '';
var attributes =
goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'iframe', attributes, opt_content);
};
/**
* Checks if the user agent supports sandboxed iframes.
* @return {boolean}
*/
goog.html.SafeHtml.canUseSandboxIframe = function() {
return goog.global['HTMLIFrameElement'] &&
('sandbox' in goog.global['HTMLIFrameElement'].prototype);
};
/**
* Creates a SafeHtml representing a script tag with the src attribute.
* @param {!goog.html.TrustedResourceUrl} src The value of the src
* attribute.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=}
* opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined
* causes the attribute to be omitted.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid attribute name or value is provided. If
* opt_attributes contains the src attribute.
*/
goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) {
// TODO(mlourenco): The charset attribute should probably be blocked. If
// its value is attacker controlled, the script contains attacker controlled
// sub-strings (even if properly escaped) and the server does not set charset
// then XSS is likely possible.
// https://html.spec.whatwg.org/multipage/scripting.html#dom-script-charset
// Check whether this is really TrustedResourceUrl.
goog.html.TrustedResourceUrl.unwrap(src);
var fixedAttributes = {'src': src};
var defaultAttributes = {};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'script', attributes);
};
/**
* Creates a SafeHtml representing a script tag. Does not allow the language,
* src, text or type attributes to be set.
* @param {!goog.html.SafeScript|!Array<!goog.html.SafeScript>}
* script Content to put inside the tag. Array elements are
* concatenated.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid attribute name or attribute value is provided. If
* opt_attributes contains the language, src, text or type attribute.
*/
goog.html.SafeHtml.createScript = function(script, opt_attributes) {
for (var attr in opt_attributes) {
var attrLower = attr.toLowerCase();
if (attrLower == 'language' || attrLower == 'src' || attrLower == 'text' ||
attrLower == 'type') {
throw Error('Cannot set "' + attrLower + '" attribute');
}
}
var content = '';
script = goog.array.concat(script);
for (var i = 0; i < script.length; i++) {
content += goog.html.SafeScript.unwrap(script[i]);
}
// Convert to SafeHtml so that it's not HTML-escaped. This is safe because
// as part of its contract, SafeScript should have no dangerous '<'.
var htmlContent =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
content, goog.i18n.bidi.Dir.NEUTRAL);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'script', opt_attributes, htmlContent);
};
/**
* Creates a SafeHtml representing a style tag. The type attribute is set
* to "text/css".
* @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
* styleSheet Content to put inside the tag. Array elements are
* concatenated.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid attribute name or attribute value is provided. If
* opt_attributes contains the type attribute.
*/
goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
var fixedAttributes = {'type': 'text/css'};
var defaultAttributes = {};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
var content = '';
styleSheet = goog.array.concat(styleSheet);
for (var i = 0; i < styleSheet.length; i++) {
content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
}
// Convert to SafeHtml so that it's not HTML-escaped. This is safe because
// as part of its contract, SafeStyleSheet should have no dangerous '<'.
var htmlContent =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
content, goog.i18n.bidi.Dir.NEUTRAL);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'style', attributes, htmlContent);
};
/**
* Creates a SafeHtml representing a meta refresh tag.
* @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is
* passed, it will be sanitized with SafeUrl.sanitize().
* @param {number=} opt_secs Number of seconds until the page should be
* reloaded. Will be set to 0 if unspecified.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
*/
goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) {
// Note that sanitize is a no-op on SafeUrl.
var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url));
if (goog.labs.userAgent.browser.isIE() ||
goog.labs.userAgent.browser.isEdge()) {
// IE/EDGE can't parse the content attribute if the url contains a
// semicolon. We can fix this by adding quotes around the url, but then we
// can't parse quotes in the URL correctly. Also, it seems that IE/EDGE
// did not unescape semicolons in these URLs at some point in the past. We
// take a best-effort approach.
//
// If the URL has semicolons (which may happen in some cases, see
// http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2
// for instance), wrap it in single quotes to protect the semicolons.
// If the URL has semicolons and single quotes, url-encode the single quotes
// as well.
//
// This is imperfect. Notice that both ' and ; are reserved characters in
// URIs, so this could do the wrong thing, but at least it will do the wrong
// thing in only rare cases.
if (goog.string.contains(unwrappedUrl, ';')) {
unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'";
}
}
var attributes = {
'http-equiv': 'refresh',
'content': (opt_secs || 0) + '; url=' + unwrappedUrl
};
// This function will handle the HTML escaping for attributes.
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'meta', attributes);
};
/**
* @param {string} tagName The tag name.
* @param {string} name The attribute name.
* @param {!goog.html.SafeHtml.AttributeValue} value The attribute value.
* @return {string} A "name=value" string.
* @throws {Error} If attribute value is unsafe for the given tag and attribute.
* @private
*/
goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
// If it's goog.string.Const, allow any valid attribute name.
if (value instanceof goog.string.Const) {
value = goog.string.Const.unwrap(value);
} else if (name.toLowerCase() == 'style') {
value = goog.html.SafeHtml.getStyleValue_(value);
} else if (/^on/i.test(name)) {
// TODO(jakubvrana): Disallow more attributes with a special meaning.
throw Error(
'Attribute "' + name + '" requires goog.string.Const value, "' + value +
'" given.');
// URL attributes handled differently according to tag.
} else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
if (value instanceof goog.html.TrustedResourceUrl) {
value = goog.html.TrustedResourceUrl.unwrap(value);
} else if (value instanceof goog.html.SafeUrl) {
value = goog.html.SafeUrl.unwrap(value);
} else if (goog.isString(value)) {
value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();
} else {
throw Error(
'Attribute "' + name + '" on tag "' + tagName +
'" requires goog.html.SafeUrl, goog.string.Const, or string,' +
' value "' + value + '" given.');
}
}
// Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
// HTML-escaping.
if (value.implementsGoogStringTypedString) {
// Ok to call getTypedStringValue() since there's no reliance on the type
// contract for security here.
value = value.getTypedStringValue();
}
goog.asserts.assert(
goog.isString(value) || goog.isNumber(value),
'String or number value expected, got ' + (typeof value) +
' with value: ' + value);
return name + '="' + goog.string.htmlEscape(String(value)) + '"';
};
/**
* Gets value allowed in "style" attribute.
* @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a
* map which will be passed to goog.html.SafeStyle.create.
* @return {string} Unwrapped value.
* @throws {Error} If string value is given.
* @private
*/
goog.html.SafeHtml.getStyleValue_ = function(value) {
if (!goog.isObject(value)) {
throw Error(
'The "style" attribute requires goog.html.SafeStyle or map ' +
'of style properties, ' + (typeof value) + ' given: ' + value);
}
if (!(value instanceof goog.html.SafeStyle)) {
// Process the property bag into a style object.
value = goog.html.SafeStyle.create(value);
}
return goog.html.SafeStyle.unwrap(value);
};
/**
* Creates a SafeHtml content with known directionality consisting of a tag with
* optional attributes and optional content.
* @param {!goog.i18n.bidi.Dir} dir Directionality.
* @param {string} tagName
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
*/
goog.html.SafeHtml.createWithDir = function(
dir, tagName, opt_attributes, opt_content) {
var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
html.dir_ = dir;
return html;
};
/**
* Creates a new SafeHtml object by concatenating values.
* @param {...(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
* @return {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.concat = function(var_args) {
var dir = goog.i18n.bidi.Dir.NEUTRAL;
var content = '';
/**
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
var html = goog.html.SafeHtml.htmlEscape(argument);
content += goog.html.SafeHtml.unwrap(html);
var htmlDir = html.getDirection();
if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
dir = htmlDir;
} else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
dir = null;
}
}
};
goog.array.forEach(arguments, addArgument);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
content, dir);
};
/**
* Creates a new SafeHtml object with known directionality by concatenating the
* values.
* @param {!goog.i18n.bidi.Dir} dir Directionality.
* @param {...(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
* arguments would be processed recursively.
* @return {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
html.dir_ = dir;
return html;
};
/**
* Type marker for the SafeHtml type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Package-internal utility method to create SafeHtml instances.
*
* @param {string} html The string to initialize the SafeHtml object with.
* @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
* constructed, or null if unknown.
* @return {!goog.html.SafeHtml} The initialized SafeHtml object.
* @package
*/
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
html, dir) {
return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
html, dir);
};
/**
* Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} html
* @param {?goog.i18n.bidi.Dir} dir
* @return {!goog.html.SafeHtml}
* @private
*/
goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
html, dir) {
this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
this.dir_ = dir;
return this;
};
/**
* Like create() but does not restrict which tags can be constructed.
*
* @param {string} tagName Tag name. Set or validated by caller.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @param {(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
* @return {!goog.html.SafeHtml}
* @throws {Error} If invalid or unsafe attribute name or value is provided.
* @throws {goog.asserts.AssertionError} If content for void tag is provided.
* @package
*/
goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function(
tagName, opt_attributes, opt_content) {
var dir = null;
var result = '<' + tagName;
result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes);
var content = opt_content;
if (!goog.isDefAndNotNull(content)) {
content = [];
} else if (!goog.isArray(content)) {
content = [content];
}
if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
goog.asserts.assert(
!content.length, 'Void tag <' + tagName + '> does not allow content.');
result += '>';
} else {
var html = goog.html.SafeHtml.concat(content);
result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
dir = html.getDirection();
}
var dirAttribute = opt_attributes && opt_attributes['dir'];
if (dirAttribute) {
if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
// If the tag has the "dir" attribute specified then its direction is
// neutral because it can be safely used in any context.
dir = goog.i18n.bidi.Dir.NEUTRAL;
} else {
dir = null;
}
}
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
result, dir);
};
/**
* Creates a string with attributes to insert after tagName.
* @param {string} tagName
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @return {string} Returns an empty string if there are no attributes, returns
* a string starting with a space otherwise.
* @throws {Error} If attribute value is unsafe for the given tag and attribute.
* @package
*/
goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) {
var result = '';
if (opt_attributes) {
for (var name in opt_attributes) {
if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
throw Error('Invalid attribute name "' + name + '".');
}
var value = opt_attributes[name];
if (!goog.isDefAndNotNull(value)) {
continue;
}
result +=
' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
}
}
return result;
};
/**
* @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>} fixedAttributes
* @param {!Object<string, string>} defaultAttributes
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Optional attributes passed to create*().
* @return {!Object<string, ?goog.html.SafeHtml.AttributeValue>}
* @throws {Error} If opt_attributes contains an attribute with the same name
* as an attribute in fixedAttributes.
* @package
*/
goog.html.SafeHtml.combineAttributes = function(
fixedAttributes, defaultAttributes, opt_attributes) {
var combinedAttributes = {};
var name;
for (name in fixedAttributes) {
goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
combinedAttributes[name] = fixedAttributes[name];
}
for (name in defaultAttributes) {
goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
combinedAttributes[name] = defaultAttributes[name];
}
for (name in opt_attributes) {
var nameLower = name.toLowerCase();
if (nameLower in fixedAttributes) {
throw Error(
'Cannot override "' + nameLower + '" attribute, got "' + name +
'" with value "' + opt_attributes[name] + '"');
}
if (nameLower in defaultAttributes) {
delete combinedAttributes[nameLower];
}
combinedAttributes[name] = opt_attributes[name];
}
return combinedAttributes;
};
/**
* A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.DOCTYPE_HTML =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
/**
* A SafeHtml instance corresponding to the empty string.
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.EMPTY =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'', goog.i18n.bidi.Dir.NEUTRAL);
/**
* A SafeHtml instance corresponding to the <br> tag.
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.BR =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'<br>', goog.i18n.bidi.Dir.NEUTRAL);

View file

@ -0,0 +1,234 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeScript type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeScript');
goog.require('goog.asserts');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* 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
* {@code goog.html.SafeScript.fromConstant} 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.
*
* 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
* {@code &lt;} is dangerous, even when inside JavaScript strings, and so should
* always be forbidden or JavaScript escaped in user controlled input. For
* example, if {@code &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 {@code 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.
*
* @see goog.html.SafeScript#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeScript = function() {
/**
* 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 {string}
*/
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeScript#unwrap
* @const {!Object}
* @private
*/
this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeScript type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a SafeScript object from a compile-time constant string.
*
* @param {!goog.string.Const} script A compile-time-constant string from which
* to create a SafeScript.
* @return {!goog.html.SafeScript} A SafeScript object initialized to
* {@code script}.
*/
goog.html.SafeScript.fromConstant = function(script) {
var scriptString = goog.string.Const.unwrap(script);
if (scriptString.length === 0) {
return goog.html.SafeScript.EMPTY;
}
return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
scriptString);
};
/**
* Returns this SafeScript's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeScript}, use {@code goog.html.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 goog.html.SafeScript#unwrap
* @override
*/
goog.html.SafeScript.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeScript, use
* {@code goog.html.SafeScript.unwrap}.
*
* @see goog.html.SafeScript#unwrap
* @override
*/
goog.html.SafeScript.prototype.toString = function() {
return 'SafeScript{' +
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeScript object, and returns its value.
*
* @param {!goog.html.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, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeScript.unwrap = function(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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeScript instanceof goog.html.SafeScript &&
safeScript.constructor === goog.html.SafeScript &&
safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeScript, got \'' +
safeScript + '\' of type ' + goog.typeOf(safeScript));
return 'type_error:SafeScript';
}
};
/**
* Package-internal utility method to create SafeScript instances.
*
* @param {string} script The string to initialize the SafeScript object with.
* @return {!goog.html.SafeScript} The initialized SafeScript object.
* @package
*/
goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse =
function(script) {
return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_(
script);
};
/**
* Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} script
* @return {!goog.html.SafeScript}
* @private
*/
goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
script) {
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
return this;
};
/**
* A SafeScript instance corresponding to the empty string.
* @const {!goog.html.SafeScript}
*/
goog.html.SafeScript.EMPTY =
goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');

View file

@ -0,0 +1,560 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeStyle type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeStyle');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.html.SafeUrl');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string-like object which represents a sequence of CSS declarations
* ({@code 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
* ({@code goog.html.SafeStyle.create} or
* {@code goog.html.SafeStyle.fromConstant}) 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.
*
* SafeStyle's string representation can safely be:
* <ul>
* <li>Interpolated as the content of a *quoted* HTML style attribute.
* However, the SafeStyle string *must be HTML-attribute-escaped* before
* interpolation.
* <li>Interpolated as the content of a {}-wrapped block within a stylesheet.
* '<' characters in the SafeStyle string *must be CSS-escaped* before
* interpolation. The SafeStyle string is also guaranteed not to be able
* to introduce new properties or elide existing ones.
* <li>Interpolated as the content of a {}-wrapped block within an HTML
* <style> element. '<' characters in the SafeStyle string
* *must be CSS-escaped* before interpolation.
* <li>Assigned to the style property of a DOM node. The SafeStyle string
* should not be escaped before being assigned to the property.
* </ul>
*
* A SafeStyle may never contain literal angle brackets. Otherwise, it could
* be unsafe to place a SafeStyle into a &lt;style&gt; tag (where it can't
* be HTML escaped). For example, if the SafeStyle containing
* "{@code 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
* {@code style1} and {@code style2} of this type,
* {@code goog.html.SafeStyle.unwrap(style1) +
* goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies
* the SafeStyle type constraint. This requirement implies that for any value
* {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must
* not end in a "property value" or "property name" context. For example,
* a value of {@code background:url("} or {@code 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 {@code javascript:evil())"} is
* appended to {@code 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 goog.html.SafeStyle#create
* @see goog.html.SafeStyle#fromConstant
* @see http://www.w3.org/TR/css3-syntax/
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeStyle = function() {
/**
* 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_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeStyle#unwrap
* @const {!Object}
* @private
*/
this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeStyle type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a SafeStyle object from a compile-time constant string.
*
* {@code style} should be in the format
* {@code 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 {@code style}
* but does not constrain the format of {@code name} and {@code value}, except
* for disallowing tag characters.
*
* @param {!goog.string.Const} style A compile-time-constant string from which
* to create a SafeStyle.
* @return {!goog.html.SafeStyle} A SafeStyle object initialized to
* {@code style}.
*/
goog.html.SafeStyle.fromConstant = function(style) {
var styleString = goog.string.Const.unwrap(style);
if (styleString.length === 0) {
return goog.html.SafeStyle.EMPTY;
}
goog.html.SafeStyle.checkStyle_(styleString);
goog.asserts.assert(
goog.string.endsWith(styleString, ';'),
'Last character of style string is not \';\': ' + styleString);
goog.asserts.assert(
goog.string.contains(styleString, ':'),
'Style string must contain at least one \':\', to ' +
'specify a "name: value" pair: ' + styleString);
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
styleString);
};
/**
* Checks if the style definition is valid.
* @param {string} style
* @private
*/
goog.html.SafeStyle.checkStyle_ = function(style) {
goog.asserts.assert(
!/[<>]/.test(style), 'Forbidden characters in style string: ' + style);
};
/**
* Returns this SafeStyle's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeStyle}, use {@code goog.html.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>
*
* @see goog.html.SafeStyle#unwrap
* @override
*/
goog.html.SafeStyle.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeStyleWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeStyle, use
* {@code goog.html.SafeStyle.unwrap}.
*
* @see goog.html.SafeStyle#unwrap
* @override
*/
goog.html.SafeStyle.prototype.toString = function() {
return 'SafeStyle{' + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ +
'}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeStyle object, and returns its value.
*
* @param {!goog.html.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, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeStyle.unwrap = function(safeStyle) {
// 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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeStyle instanceof goog.html.SafeStyle &&
safeStyle.constructor === goog.html.SafeStyle &&
safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
} else {
goog.asserts.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 {!goog.html.SafeStyle} The initialized SafeStyle object.
* @package
*/
goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = function(
style) {
return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style);
};
/**
* Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} style
* @return {!goog.html.SafeStyle}
* @private
*/
goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
style) {
this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style;
return this;
};
/**
* A SafeStyle instance corresponding to the empty string.
* @const {!goog.html.SafeStyle}
*/
goog.html.SafeStyle.EMPTY =
goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
/**
* The innocuous string generated by goog.html.SafeStyle.create when passed
* an unsafe value.
* @const {string}
*/
goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
/**
* A single property value.
* @typedef {string|!goog.string.Const|!goog.html.SafeUrl}
*/
goog.html.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, ?goog.html.SafeStyle.PropertyValue|
* ?Array<!goog.html.SafeStyle.PropertyValue>>}
*/
goog.html.SafeStyle.PropertyMap;
/**
* Creates a new SafeStyle object from the properties specified in the map.
* @param {goog.html.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 goog.string.Const. URLs might be passed
* as goog.html.SafeUrl which will be wrapped into url(""). We also support
* array whose elements are joined with ' '. Null value causes skipping the
* property.
* @return {!goog.html.SafeStyle}
* @throws {Error} If invalid name is provided.
* @throws {goog.asserts.AssertionError} If invalid value is provided. With
* disabled assertions, invalid value is replaced by
* goog.html.SafeStyle.INNOCUOUS_STRING.
*/
goog.html.SafeStyle.create = function(map) {
var style = '';
for (var name in map) {
if (!/^[-_a-zA-Z0-9]+$/.test(name)) {
throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name);
}
var value = map[name];
if (value == null) {
continue;
}
if (goog.isArray(value)) {
value = goog.array.map(value, goog.html.SafeStyle.sanitizePropertyValue_)
.join(' ');
} else {
value = goog.html.SafeStyle.sanitizePropertyValue_(value);
}
style += name + ':' + value + ';';
}
if (!style) {
return goog.html.SafeStyle.EMPTY;
}
goog.html.SafeStyle.checkStyle_(style);
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};
/**
* Checks and converts value to string.
* @param {!goog.html.SafeStyle.PropertyValue} value
* @return {string}
* @private
*/
goog.html.SafeStyle.sanitizePropertyValue_ = function(value) {
if (value instanceof goog.html.SafeUrl) {
var url = goog.html.SafeUrl.unwrap(value);
return 'url("' + url.replace(/</g, '%3c').replace(/[\\"]/g, '\\$&') + '")';
}
var result = value instanceof goog.string.Const ?
goog.string.Const.unwrap(value) :
goog.html.SafeStyle.sanitizePropertyValueString_(String(value));
// These characters can be used to change context and we don't want that even
// with const values.
goog.asserts.assert(!/[{;}]/.test(result), 'Value does not allow [{;}].');
return result;
};
/**
* Checks string value.
* @param {string} value
* @return {string}
* @private
*/
goog.html.SafeStyle.sanitizePropertyValueString_ = function(value) {
var valueWithoutFunctions =
value.replace(goog.html.SafeUrl.FUNCTIONS_RE_, '$1')
.replace(goog.html.SafeUrl.URL_RE_, 'url');
if (!goog.html.SafeStyle.VALUE_RE_.test(valueWithoutFunctions)) {
goog.asserts.fail(
'String value allows only ' + goog.html.SafeStyle.VALUE_ALLOWED_CHARS_ +
' and simple functions, got: ' + value);
return goog.html.SafeStyle.INNOCUOUS_STRING;
} else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) {
goog.asserts.fail('String value requires balanced quotes, got: ' + value);
return goog.html.SafeStyle.INNOCUOUS_STRING;
}
return goog.html.SafeStyle.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.
* @private
*/
goog.html.SafeStyle.hasBalancedQuotes_ = function(value) {
var outsideSingle = true;
var outsideDouble = true;
for (var i = 0; i < value.length; i++) {
var c = value.charAt(i);
if (c == "'" && outsideDouble) {
outsideSingle = !outsideSingle;
} else if (c == '"' && outsideSingle) {
outsideDouble = !outsideDouble;
}
}
return outsideSingle && outsideDouble;
};
/**
* Characters allowed in goog.html.SafeStyle.VALUE_RE_.
* @private {string}
*/
goog.html.SafeStyle.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.
*
* ',' 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}
* @private
*/
goog.html.SafeStyle.VALUE_RE_ =
new RegExp('^' + goog.html.SafeStyle.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.
* @private @const {!RegExp}
*/
goog.html.SafeUrl.URL_RE_ = new RegExp(
'\\b(url\\([ \t\n]*)(' +
'\'[ -&(-\\[\\]-~]*\'' + // Printable characters except ' and \.
'|"[ !#-\\[\\]-~]*"' + // Printable characters except " and \.
'|[!#-&*-\\[\\]-~]*' + // Printable characters except [ "'()\\].
')([ \t\n]*\\))',
'g');
/**
* Regular expression for simple functions.
* @private @const {!RegExp}
*/
goog.html.SafeUrl.FUNCTIONS_RE_ = new RegExp(
'\\b(hsl|hsla|rgb|rgba|(rotate|scale|translate)(X|Y|Z|3d)?)' +
'\\([-0-9a-z.%, ]+\\)',
'g');
/**
* 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}
* @private
*/
goog.html.SafeStyle.sanitizeUrl_ = function(value) {
return value.replace(
goog.html.SafeUrl.URL_RE_, function(match, before, url, after) {
var quote = '';
url = url.replace(/^(['"])(.*)\1$/, function(match, start, inside) {
quote = start;
return inside;
});
var sanitized = goog.html.SafeUrl.sanitize(url).getTypedStringValue();
return before + quote + sanitized + quote + after;
});
};
/**
* Creates a new SafeStyle object by concatenating the values.
* @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args
* SafeStyles to concatenate.
* @return {!goog.html.SafeStyle}
*/
goog.html.SafeStyle.concat = function(var_args) {
var style = '';
/**
* @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
style += goog.html.SafeStyle.unwrap(argument);
}
};
goog.array.forEach(arguments, addArgument);
if (!style) {
return goog.html.SafeStyle.EMPTY;
}
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};

View file

@ -0,0 +1,344 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeStyleSheet type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeStyleSheet');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.html.SafeStyle');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* 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
* {@code goog.html.SafeStyleSheet.fromConstant} 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.
*
* 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
* {@code styleSheet1} and {@code styleSheet2} of this type,
* {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) +
* goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that
* satisfies the SafeStyleSheet type constraint. This requirement implies that
* for any value {@code styleSheet} of this type,
* {@code goog.html.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 {@code &lt;} is dangerous, even when inside CSS strings, and so should
* always be forbidden or CSS-escaped in user controlled input. For example, if
* {@code &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 {@code evil} would execute. Also note that within an HTML
* style (raw text) element, HTML character references, such as
* {@code &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 goog.html.SafeStyleSheet#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeStyleSheet = function() {
/**
* 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_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeStyleSheet#unwrap
* @const {!Object}
* @private
*/
this.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeStyleSheet type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a style sheet consisting of one selector and one style definition.
* Use {@link goog.html.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 {!goog.html.SafeStyle.PropertyMap|!goog.html.SafeStyle} style Style
* definition associated with the selector.
* @return {!goog.html.SafeStyleSheet}
* @throws {Error} If invalid selector is provided.
*/
goog.html.SafeStyleSheet.createRule = function(selector, style) {
if (goog.string.contains(selector, '<')) {
throw Error('Selector does not allow \'<\', got: ' + selector);
}
// Remove strings.
var 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 Error(
'Selector allows only [-_a-zA-Z0-9#.:* ,>+~[\\]()=^$|] and ' +
'strings, got: ' + selector);
}
// Check balanced () and [].
if (!goog.html.SafeStyleSheet.hasBalancedBrackets_(selectorToCheck)) {
throw Error('() and [] in selector must be balanced, got: ' + selector);
}
if (!(style instanceof goog.html.SafeStyle)) {
style = goog.html.SafeStyle.create(style);
}
var styleSheet = selector + '{' + goog.html.SafeStyle.unwrap(style) + '}';
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
};
/**
* Checks if a string has balanced () and [] brackets.
* @param {string} s String to check.
* @return {boolean}
* @private
*/
goog.html.SafeStyleSheet.hasBalancedBrackets_ = function(s) {
var brackets = {'(': ')', '[': ']'};
var expectedBrackets = [];
for (var i = 0; i < s.length; i++) {
var ch = s[i];
if (brackets[ch]) {
expectedBrackets.push(brackets[ch]);
} else if (goog.object.contains(brackets, ch)) {
if (expectedBrackets.pop() != ch) {
return false;
}
}
}
return expectedBrackets.length == 0;
};
/**
* Creates a new SafeStyleSheet object by concatenating values.
* @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)}
* var_args Values to concatenate.
* @return {!goog.html.SafeStyleSheet}
*/
goog.html.SafeStyleSheet.concat = function(var_args) {
var result = '';
/**
* @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
* argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
result += goog.html.SafeStyleSheet.unwrap(argument);
}
};
goog.array.forEach(arguments, addArgument);
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
};
/**
* Creates a SafeStyleSheet object from a compile-time constant string.
*
* {@code styleSheet} must not have any &lt; characters in it, so that
* the syntactic structure of the surrounding HTML is not affected.
*
* @param {!goog.string.Const} styleSheet A compile-time-constant string from
* which to create a SafeStyleSheet.
* @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to
* {@code styleSheet}.
*/
goog.html.SafeStyleSheet.fromConstant = function(styleSheet) {
var styleSheetString = goog.string.Const.unwrap(styleSheet);
if (styleSheetString.length === 0) {
return goog.html.SafeStyleSheet.EMPTY;
}
// > is a valid character in CSS selectors and there's no strict need to
// block it if we already block <.
goog.asserts.assert(
!goog.string.contains(styleSheetString, '<'),
"Forbidden '<' character in style sheet string: " + styleSheetString);
return goog.html.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 {@code SafeStyleSheet}, use {@code goog.html.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 goog.html.SafeStyleSheet#unwrap
* @override
*/
goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeStyleSheet, use
* {@code goog.html.SafeStyleSheet.unwrap}.
*
* @see goog.html.SafeStyleSheet#unwrap
* @override
*/
goog.html.SafeStyleSheet.prototype.toString = function() {
return 'SafeStyleSheet{' +
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeStyleSheet object, and returns its value.
*
* @param {!goog.html.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, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeStyleSheet.unwrap = function(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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeStyleSheet instanceof goog.html.SafeStyleSheet &&
safeStyleSheet.constructor === goog.html.SafeStyleSheet &&
safeStyleSheet
.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
} else {
goog.asserts.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 {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object.
* @package
*/
goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse =
function(styleSheet) {
return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_(
styleSheet);
};
/**
* Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} styleSheet
* @return {!goog.html.SafeStyleSheet}
* @private
*/
goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ =
function(styleSheet) {
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet;
return this;
};
/**
* A SafeStyleSheet instance corresponding to the empty string.
* @const {!goog.html.SafeStyleSheet}
*/
goog.html.SafeStyleSheet.EMPTY =
goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');

View file

@ -0,0 +1,454 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* 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 {@code 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
* ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}),
* etc 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.
*
* @see goog.html.SafeUrl#fromConstant
* @see goog.html.SafeUrl#from
* @see goog.html.SafeUrl#sanitize
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.SafeUrl = function() {
/**
* 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.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeUrl#unwrap
* @const {!Object}
* @private
*/
this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* 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 a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeUrl}, use {@code 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. 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>
*
* 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() {
return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
};
/**
* @override
* @const
*/
goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
/**
* Returns this URLs directionality, which is always {@code LTR}.
* @override
*/
goog.html.SafeUrl.prototype.getDirection = function() {
return goog.i18n.bidi.Dir.LTR;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeUrl, use
* {@code goog.html.SafeUrl.unwrap}.
*
* @see goog.html.SafeUrl#unwrap
* @override
*/
goog.html.SafeUrl.prototype.toString = function() {
return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
'}';
};
}
/**
* 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, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeUrl.unwrap = function(safeUrl) {
// 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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeUrl instanceof goog.html.SafeUrl &&
safeUrl.constructor === goog.html.SafeUrl &&
safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
} 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 {@code url}.
*/
goog.html.SafeUrl.fromConstant = function(url) {
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.
* @const
* @private
*/
goog.html.SAFE_MIME_TYPE_PATTERN_ = new RegExp(
'^(?:audio/(?:3gpp|3gpp2|aac|midi|mp4|mpeg|ogg|x-m4a|x-wav|webm)|' +
'image/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|' +
'text/csv|' +
'video/(?:mpeg|mp4|ogg|webm))$',
'i');
/**
* Creates a SafeUrl wrapping a blob URL for the given {@code blob}.
*
* The blob URL is created with {@code URL.createObjectURL}. If the MIME type
* for {@code blob} is not of a known safe audio, image or video MIME type,
* then the SafeUrl will wrap {@link #INNOCUOUS_STRING}.
*
* @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) {
var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ?
goog.fs.url.createObjectUrl(blob) :
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;
/**
* Creates a SafeUrl wrapping a data: URL, after validating it matches a
* known-safe audio, image or video MIME type.
*
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
* audio, image or video MIME types.
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
* wrapped as a SafeUrl if it does not pass.
*/
goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
// There's a slight risk here that a browser sniffs the content type if it
// doesn't know the MIME type and executes HTML within the data: URL. For this
// to cause XSS it would also have to execute the HTML in the same origin
// of the page with the link. It seems unlikely that both of these will
// happen, particularly in not really old IEs.
var match = dataUrl.match(goog.html.DATA_URL_PATTERN_);
var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]);
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING);
};
/**
* 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) {
// 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.caseInsensitiveStartsWith(telUrl, 'tel:')) {
telUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
}
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
telUrl);
};
/**
* 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) {
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;
/**
* Creates a SafeUrl object from {@code url}. If {@code 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.
*
* {@code 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).
*
* @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) {
if (url instanceof goog.html.SafeUrl) {
return url;
} else if (url.implementsGoogStringTypedString) {
url = url.getTypedStringValue();
} else {
url = String(url);
}
if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
url = goog.html.SafeUrl.INNOCUOUS_STRING;
}
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Creates a SafeUrl object from {@code url}. If {@code 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.
*
* {@code 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 innocous URL will be
* returned.
*
* @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.sanitizeAssertUnchanged = function(url) {
if (url instanceof goog.html.SafeUrl) {
return url;
} else if (url.implementsGoogStringTypedString) {
url = url.getTypedStringValue();
} else {
url = String(url);
}
if (!goog.asserts.assert(goog.html.SAFE_URL_PATTERN_.test(url))) {
url = goog.html.SafeUrl.INNOCUOUS_STRING;
}
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Type marker for the SafeUrl type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_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) {
var safeUrl = new goog.html.SafeUrl();
safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url;
return safeUrl;
};
/**
* A SafeUrl corresponding to the special about:blank url.
* @const {!goog.html.SafeUrl}
*/
goog.html.SafeUrl.ABOUT_BLANK =
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
'about:blank');

View file

@ -0,0 +1,408 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.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,
* ({@code fromConstant}, {@code fromConstants}, {@code format} or {@code
* formatWithParams}), 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.
*
* @see goog.html.TrustedResourceUrl#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.TrustedResourceUrl = function() {
/**
* 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.
* @private {string}
*/
this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.TrustedResourceUrl#unwrap
* @const {!Object}
* @private
*/
this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @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 {@code TrustedResourceUrl}, use
* {@code 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() {
return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
};
/**
* @override
* @const
*/
goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
true;
/**
* Returns this URLs directionality, which is always {@code LTR}.
* @override
*/
goog.html.TrustedResourceUrl.prototype.getDirection = function() {
return goog.i18n.bidi.Dir.LTR;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a TrustedResourceUrl, use
* {@code goog.html.TrustedResourceUrl.unwrap}.
*
* @see goog.html.TrustedResourceUrl#unwrap
* @override
*/
goog.html.TrustedResourceUrl.prototype.toString = function() {
return 'TrustedResourceUrl{' +
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, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) {
// 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.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl &&
trustedResourceUrl.constructor === goog.html.TrustedResourceUrl &&
trustedResourceUrl
.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.TrustedResourceUrl
.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
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 start with one of the following:
* - `https://<origin>/`
* - `//<origin>/`
* - `/<pathStart>`
* - `about:blank`
*
* `<origin>` must contain only alphanumeric or any of the following: `-.:[]`.
* `<pathStart>` is any character except `/` and `\`.
*
* 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.EMPTY : goog.string.Const.from('&autoplay=1')
* });
*
* 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) {
var result = goog.html.TrustedResourceUrl.format_(format, args);
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(result);
};
/**
* String version of TrustedResourceUrl.format.
* @param {!goog.string.Const} format
* @param {!Object<string, (string|number|!goog.string.Const)>} args
* @return {string}
* @throws {!Error}
* @private
*/
goog.html.TrustedResourceUrl.format_ = function(format, args) {
var formatStr = goog.string.Const.unwrap(format);
if (!goog.html.TrustedResourceUrl.BASE_URL_.test(formatStr)) {
throw new Error('Invalid TrustedResourceUrl format: ' + formatStr);
}
return formatStr.replace(
goog.html.TrustedResourceUrl.FORMAT_MARKER_, function(match, id) {
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));
}
});
};
/**
* @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.
* - / not followed by / or \. There will only be an absolute 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_ =
/^(?:https:)?\/\/[0-9a-z.:[\]-]+\/|^\/[^\/\\]|^about:blank(#|$)/i;
/**
* 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 {!Object<string, *>} params Parameters to add to URL. Parameters with
* value {@code null} or {@code undefined} are skipped. Both keys and values
* are encoded. 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.
* @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, params) {
var url = goog.html.TrustedResourceUrl.format_(format, args);
var separator = /\?/.test(url) ? '&' : '?';
for (var key in params) {
if (params[key] == null) {
continue;
}
url += separator + encodeURIComponent(key) + '=' +
encodeURIComponent(String(params[key]));
separator = '&';
}
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* 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 {@code url}.
*/
goog.html.TrustedResourceUrl.fromConstant = function(url) {
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 {@code parts}.
*/
goog.html.TrustedResourceUrl.fromConstants = function(parts) {
var unwrapped = '';
for (var i = 0; i < parts.length; i++) {
unwrapped += goog.string.Const.unwrap(parts[i]);
}
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(unwrapped);
};
/**
* Type marker for the TrustedResourceUrl type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_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) {
var trustedResourceUrl = new goog.html.TrustedResourceUrl();
trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ =
url;
return trustedResourceUrl;
};

View file

@ -0,0 +1,228 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.
*
*
*
* @visibility {//closure/goog/html:approved_for_unchecked_conversion}
* @visibility {//closure/goog/bin/sizetests:__pkg__}
*/
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');
goog.require('goog.string.Const');
/**
* 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 {@code 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) {
// 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.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 {@code 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 {@code script}, wrapped in a
* SafeScript object.
*/
goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
function(justification, script) {
// 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.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 {@code 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 {@code style}, wrapped in a
* SafeStyle object.
*/
goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
function(justification, style) {
// 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.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 {@code 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 {@code styleSheet}, wrapped
* in a SafeStyleSheet object.
*/
goog.html.uncheckedconversions
.safeStyleSheetFromStringKnownToSatisfyTypeContract = function(
justification, styleSheet) {
// 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.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 {@code 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 {@code url}, wrapped in a SafeUrl
* object.
*/
goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
function(justification, url) {
// 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.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 {@code 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 {@code url}, wrapped in
* a TrustedResourceUrl object.
*/
goog.html.uncheckedconversions
.trustedResourceUrlFromStringKnownToSatisfyTypeContract = function(
justification, url) {
// 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.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};

View file

@ -0,0 +1,876 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.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.
*/
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) == '_')) ||
(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) == '_'));
/**
* 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) {
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 characters. This pattern is not
* theoretically correct according to the Unicode standard. It is simplified for
* performance and small code size.
* @type {string}
* @private
*/
goog.i18n.bidi.ltrChars_ =
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
'\u200E\u2C00-\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.
* @type {string}
* @private
*/
goog.i18n.bidi.rtlChars_ =
'\u0591-\u06EF\u06FA-\u07FF\u200F\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) {
return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : str;
};
/**
* Regular expression to check for RTL characters.
* @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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
'^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
'.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
'(?!.*[-_](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, N'Ko, and
* Tifinagh, which also have significant modern usage. 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|Tfng 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) {
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) {
var useRtl = opt_isRtlContext === undefined ? goog.i18n.bidi.hasAnyRtl(s) :
opt_isRtlContext;
var 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 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 start with with 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) {
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) {
return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
};
/**
* Enforce the html snippet in RTL directionality regardless 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 start with with 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) {
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) {
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) {
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) {
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.
*
* @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 {@code str}.
*/
goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
var rtlCount = 0;
var totalCount = 0;
var hasWeaklyLtr = false;
var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)
.split(goog.i18n.bidi.wordSeparatorRe_);
for (var i = 0; i < tokens.length; i++) {
var 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) {
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.
*/
goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
if (element) {
dir = goog.i18n.bidi.toDir(dir);
if (dir) {
element.style.textAlign = dir == goog.i18n.bidi.Dir.RTL ?
goog.i18n.bidi.RIGHT :
goog.i18n.bidi.LEFT;
element.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
*/
goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) {
switch (goog.i18n.bidi.estimateDirection(text)) {
case (goog.i18n.bidi.Dir.LTR):
element.dir = 'ltr';
break;
case (goog.i18n.bidi.Dir.RTL):
element.dir = 'rtl';
break;
default:
// Default for no direction, inherit from document.
element.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 {@code 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;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.json.hybrid');
goog.require('goog.asserts');
goog.require('goog.json');
/**
* Attempts to serialize the JSON string natively, falling back to
* {@code 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) {
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 {@code 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) {
if (goog.global.JSON) {
try {
var obj = goog.global.JSON.parse(jsonString);
goog.asserts.assertObject(obj);
return obj;
} catch (e) {
// Native parse failed. Fall through to retry with goog.json.parse.
}
}
var obj = fallbackParser(jsonString);
goog.asserts.assert(obj);
return obj;
};
/**
* Attempts to parse the JSON string natively, falling back to
* {@code 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) {
return goog.json.hybrid.parse_(jsonString, goog.json.parse);
};

View file

@ -0,0 +1,418 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview JSON utility functions.
* @author arv@google.com (Erik Arvidsson)
*/
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 {@code 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 {@code eval}.
*/
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 {@code eval} instead. This is useful when
* transitioning to {@code goog.json.USE_NATIVE_JSON}. The error logger needs to
* be set by {@code goog.json.setErrorLogger}. If it is not set then the error
* is ignored.
*/
goog.define('goog.json.TRY_NATIVE_JSON', false);
/**
* 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) {
// 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.
var backslashesRe = /\\["\\\/bfnrtu]/g;
var simpleValuesRe =
/(?:"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)[\s\u2028\u2029]*(?=:|,|]|}|$)/g;
var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
return remainderRe.test(
s.replace(backslashesRe, '@')
.replace(simpleValuesRe, ']')
.replace(openBracketsRe, ''));
};
/**
* Logs a parsing error in {@code JSON.parse} solvable by using {@code eval}
* if {@code 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 {@code JSON.parse}.
*/
goog.json.errorLogger_ = goog.nullFunction;
/**
* Sets an error logger to use if there's a recoverable parsing error and {@code
* 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 {@code JSON.parse}.
*/
goog.json.setErrorLogger = function(errorLogger) {
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.
*/
goog.json.parse = goog.json.USE_NATIVE_JSON ?
/** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
function(s) {
var error;
if (goog.json.TRY_NATIVE_JSON) {
try {
return goog.global['JSON']['parse'](s);
} catch (ex) {
error = ex;
}
}
var o = String(s);
if (goog.json.isValid(o)) {
try {
var result = /** @type {?Object} */ (eval('(' + o + ')'));
if (error) {
goog.json.errorLogger_('Invalid JSON: ' + o, error);
}
return result;
} catch (ex) {
}
}
throw Error('Invalid JSON string: ' + o);
};
/**
* Parses a JSON string and returns the result. This uses eval so it is open
* to security issues and it should only be used if you trust the source.
*
* @param {string} s The JSON string to parse.
* @return {Object} The object generated from the JSON string.
* @deprecated Use JSON.parse if possible or goog.json.parse.
*/
goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?
/** @type {function(string):Object} */ (goog.global['JSON']['parse']) :
function(s) {
var error;
if (goog.json.TRY_NATIVE_JSON) {
try {
return goog.global['JSON']['parse'](s);
} catch (ex) {
error = ex;
}
}
var result = /** @type {?Object} */ (eval('(' + s + ')'));
if (error) {
goog.json.errorLogger_('Invalid JSON: ' + s, error);
}
return result;
};
/**
* 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) {
// 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) {
/**
* @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) {
var 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) {
if (object == null) {
// undefined == null so this branch covers undefined as well as null
sb.push('null');
return;
}
if (typeof object == 'object') {
if (goog.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 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) {
// The official JSON implementation does not work with international
// characters.
sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
// caching the result improves performance by a factor 2-3
var 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) {
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) {
var l = arr.length;
sb.push('[');
var sep = '';
for (var i = 0; i < l; i++) {
sb.push(sep);
var 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) {
sb.push('{');
var sep = '';
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var 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('}');
};

View file

@ -0,0 +1,338 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.)
*
* @author martone@google.com (Andy Martone)
*/
goog.provide('goog.labs.userAgent.browser');
goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.object');
goog.require('goog.string');
// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
// functions.
/**
* @return {boolean} Whether the user's browser is Opera. Note: Chromium
* based Opera (Opera 15+) is detected as Chrome to avoid unnecessary
* special casing.
* @private
*/
goog.labs.userAgent.browser.matchOpera_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Opera');
};
/**
* @return {boolean} Whether the user's browser is IE.
* @private
*/
goog.labs.userAgent.browser.matchIE_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Trident') ||
goog.labs.userAgent.util.matchUserAgent('MSIE');
};
/**
* @return {boolean} Whether the user's browser is Edge.
* @private
*/
goog.labs.userAgent.browser.matchEdge_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Edge');
};
/**
* @return {boolean} Whether the user's browser is Firefox.
* @private
*/
goog.labs.userAgent.browser.matchFirefox_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Firefox');
};
/**
* @return {boolean} Whether the user's browser is Safari.
* @private
*/
goog.labs.userAgent.browser.matchSafari_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Safari') &&
!(goog.labs.userAgent.browser.matchChrome_() ||
goog.labs.userAgent.browser.matchCoast_() ||
goog.labs.userAgent.browser.matchOpera_() ||
goog.labs.userAgent.browser.matchEdge_() ||
goog.labs.userAgent.browser.isSilk() ||
goog.labs.userAgent.util.matchUserAgent('Android'));
};
/**
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
* iOS browser).
* @private
*/
goog.labs.userAgent.browser.matchCoast_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Coast');
};
/**
* @return {boolean} Whether the user's browser is iOS Webview.
* @private
*/
goog.labs.userAgent.browser.matchIosWebview_ = function() {
// iOS Webview does not show up as Chrome or Safari. Also check for Opera's
// WebKit-based iOS browser, Coast.
return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
!goog.labs.userAgent.browser.matchSafari_() &&
!goog.labs.userAgent.browser.matchChrome_() &&
!goog.labs.userAgent.browser.matchCoast_() &&
goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
};
/**
* @return {boolean} Whether the user's browser is Chrome.
* @private
*/
goog.labs.userAgent.browser.matchChrome_ = function() {
return (goog.labs.userAgent.util.matchUserAgent('Chrome') ||
goog.labs.userAgent.util.matchUserAgent('CriOS')) &&
!goog.labs.userAgent.browser.matchEdge_();
};
/**
* @return {boolean} Whether the user's browser is the Android browser.
* @private
*/
goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
// Android can appear in the user agent string for Chrome on Android.
// This is not the Android standalone browser if it does.
return goog.labs.userAgent.util.matchUserAgent('Android') &&
!(goog.labs.userAgent.browser.isChrome() ||
goog.labs.userAgent.browser.isFirefox() ||
goog.labs.userAgent.browser.isOpera() ||
goog.labs.userAgent.browser.isSilk());
};
/**
* @return {boolean} Whether the user's browser is Opera.
*/
goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
/**
* @return {boolean} Whether the user's browser is IE.
*/
goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
/**
* @return {boolean} Whether the user's browser is Edge.
*/
goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_;
/**
* @return {boolean} Whether the user's browser is Firefox.
*/
goog.labs.userAgent.browser.isFirefox =
goog.labs.userAgent.browser.matchFirefox_;
/**
* @return {boolean} Whether the user's browser is Safari.
*/
goog.labs.userAgent.browser.isSafari = goog.labs.userAgent.browser.matchSafari_;
/**
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
* iOS browser).
*/
goog.labs.userAgent.browser.isCoast = goog.labs.userAgent.browser.matchCoast_;
/**
* @return {boolean} Whether the user's browser is iOS Webview.
*/
goog.labs.userAgent.browser.isIosWebview =
goog.labs.userAgent.browser.matchIosWebview_;
/**
* @return {boolean} Whether the user's browser is Chrome.
*/
goog.labs.userAgent.browser.isChrome = goog.labs.userAgent.browser.matchChrome_;
/**
* @return {boolean} Whether the user's browser is the Android browser.
*/
goog.labs.userAgent.browser.isAndroidBrowser =
goog.labs.userAgent.browser.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.
*/
goog.labs.userAgent.browser.isSilk = function() {
return goog.labs.userAgent.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.)
*/
goog.labs.userAgent.browser.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
// Special case IE since IE's version is inside the parenthesis and
// without the '/'.
if (goog.labs.userAgent.browser.isIE()) {
return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
}
var versionTuples =
goog.labs.userAgent.util.extractVersionTuples(userAgentString);
// Construct a map for easy lookup.
var versionMap = {};
goog.array.forEach(versionTuples, function(tuple) {
// Note that the tuple is of length three, but we only care about the
// first two.
var key = tuple[0];
var value = tuple[1];
versionMap[key] = value;
});
var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);
// Gives the value with the first key it finds, otherwise empty string.
function lookUpValueWithKeys(keys) {
var key = goog.array.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 (goog.labs.userAgent.browser.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 (goog.labs.userAgent.browser.isEdge()) {
return lookUpValueWithKeys(['Edge']);
}
if (goog.labs.userAgent.browser.isChrome()) {
return lookUpValueWithKeys(['Chrome', 'CriOS']);
}
// Usually products browser versions are in the third tuple after "Mozilla"
// and the engine.
var 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.
*/
goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.browser.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}
* @private
*/
goog.labs.userAgent.browser.getIEVersion_ = function(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.
var rv = /rv: *([\d\.]*)/.exec(userAgent);
if (rv && rv[1]) {
return rv[1];
}
var version = '';
var 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.
var 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;
};

View file

@ -0,0 +1,156 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.provide('goog.labs.userAgent.engine');
goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* @return {boolean} Whether the rendering engine is Presto.
*/
goog.labs.userAgent.engine.isPresto = function() {
return goog.labs.userAgent.util.matchUserAgent('Presto');
};
/**
* @return {boolean} Whether the rendering engine is Trident.
*/
goog.labs.userAgent.engine.isTrident = function() {
// IE only started including the Trident token in IE8.
return goog.labs.userAgent.util.matchUserAgent('Trident') ||
goog.labs.userAgent.util.matchUserAgent('MSIE');
};
/**
* @return {boolean} Whether the rendering engine is Edge.
*/
goog.labs.userAgent.engine.isEdge = function() {
return goog.labs.userAgent.util.matchUserAgent('Edge');
};
/**
* @return {boolean} Whether the rendering engine is WebKit.
*/
goog.labs.userAgent.engine.isWebKit = function() {
return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
!goog.labs.userAgent.engine.isEdge();
};
/**
* @return {boolean} Whether the rendering engine is Gecko.
*/
goog.labs.userAgent.engine.isGecko = function() {
return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
!goog.labs.userAgent.engine.isWebKit() &&
!goog.labs.userAgent.engine.isTrident() &&
!goog.labs.userAgent.engine.isEdge();
};
/**
* @return {string} The rendering engine's version or empty string if version
* can't be determined.
*/
goog.labs.userAgent.engine.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
if (userAgentString) {
var tuples = goog.labs.userAgent.util.extractVersionTuples(userAgentString);
var engineTuple = goog.labs.userAgent.engine.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 goog.labs.userAgent.engine.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.
var browserTuple = tuples[0];
var info;
if (browserTuple && (info = browserTuple[2])) {
var 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.
* @private
*/
goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
if (!goog.labs.userAgent.engine.isEdge()) {
return tuples[1];
}
for (var i = 0; i < tuples.length; i++) {
var 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.
*/
goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.engine.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.
* @private
*/
goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
// TODO(nnaze): Move to util if useful elsewhere.
var pair = goog.array.find(tuples, function(pair) { return key == pair[0]; });
return pair && pair[1] || '';
};

View file

@ -0,0 +1,160 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.provide('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* @return {boolean} Whether the platform is Android.
*/
goog.labs.userAgent.platform.isAndroid = function() {
return goog.labs.userAgent.util.matchUserAgent('Android');
};
/**
* @return {boolean} Whether the platform is iPod.
*/
goog.labs.userAgent.platform.isIpod = function() {
return goog.labs.userAgent.util.matchUserAgent('iPod');
};
/**
* @return {boolean} Whether the platform is iPhone.
*/
goog.labs.userAgent.platform.isIphone = function() {
return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
!goog.labs.userAgent.util.matchUserAgent('iPod') &&
!goog.labs.userAgent.util.matchUserAgent('iPad');
};
/**
* @return {boolean} Whether the platform is iPad.
*/
goog.labs.userAgent.platform.isIpad = function() {
return goog.labs.userAgent.util.matchUserAgent('iPad');
};
/**
* @return {boolean} Whether the platform is iOS.
*/
goog.labs.userAgent.platform.isIos = function() {
return goog.labs.userAgent.platform.isIphone() ||
goog.labs.userAgent.platform.isIpad() ||
goog.labs.userAgent.platform.isIpod();
};
/**
* @return {boolean} Whether the platform is Mac.
*/
goog.labs.userAgent.platform.isMacintosh = function() {
return goog.labs.userAgent.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.
*/
goog.labs.userAgent.platform.isLinux = function() {
return goog.labs.userAgent.util.matchUserAgent('Linux');
};
/**
* @return {boolean} Whether the platform is Windows.
*/
goog.labs.userAgent.platform.isWindows = function() {
return goog.labs.userAgent.util.matchUserAgent('Windows');
};
/**
* @return {boolean} Whether the platform is ChromeOS.
*/
goog.labs.userAgent.platform.isChromeOS = function() {
return goog.labs.userAgent.util.matchUserAgent('CrOS');
};
/**
* 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.
*/
goog.labs.userAgent.platform.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
var version = '', re;
if (goog.labs.userAgent.platform.isWindows()) {
re = /Windows (?:NT|Phone) ([0-9.]+)/;
var match = re.exec(userAgentString);
if (match) {
version = match[1];
} else {
version = '0.0';
}
} else if (goog.labs.userAgent.platform.isIos()) {
re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
var match = re.exec(userAgentString);
// Report the version as x.y.z and not x_y_z
version = match && match[1].replace(/_/g, '.');
} else if (goog.labs.userAgent.platform.isMacintosh()) {
re = /Mac OS X ([0-9_.]+)/;
var 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 (goog.labs.userAgent.platform.isAndroid()) {
re = /Android\s+([^\);]+)(\)|;)/;
var match = re.exec(userAgentString);
version = match && match[1];
} else if (goog.labs.userAgent.platform.isChromeOS()) {
re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
var 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.
*/
goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.platform.getVersion(), version) >= 0;
};

View file

@ -0,0 +1,147 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities used by goog.labs.userAgent tools. These functions
* should not be used outside of goog.labs.userAgent.*.
*
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* Gets the native userAgent string from navigator if it exists.
* If navigator or navigator.userAgent string is missing, returns an empty
* string.
* @return {string}
* @private
*/
goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
var navigator = goog.labs.userAgent.util.getNavigator_();
if (navigator) {
var userAgent = navigator.userAgent;
if (userAgent) {
return userAgent;
}
}
return '';
};
/**
* Getter for the native navigator.
* This is a separate function so it can be stubbed out in testing.
* @return {Navigator}
* @private
*/
goog.labs.userAgent.util.getNavigator_ = function() {
return goog.global.navigator;
};
/**
* A possible override for applications which wish to not check
* navigator.userAgent but use a specified value for detection instead.
* @private {string}
*/
goog.labs.userAgent.util.userAgent_ =
goog.labs.userAgent.util.getNativeUserAgentString_();
/**
* Applications may override browser detection on the built in
* navigator.userAgent object by setting this string. Set to null to use the
* browser object instead.
* @param {?string=} opt_userAgent The User-Agent override.
*/
goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
goog.labs.userAgent.util.userAgent_ =
opt_userAgent || goog.labs.userAgent.util.getNativeUserAgentString_();
};
/**
* @return {string} The user agent string.
*/
goog.labs.userAgent.util.getUserAgent = function() {
return goog.labs.userAgent.util.userAgent_;
};
/**
* @param {string} str
* @return {boolean} Whether the user agent contains the given string.
*/
goog.labs.userAgent.util.matchUserAgent = function(str) {
var userAgent = goog.labs.userAgent.util.getUserAgent();
return goog.string.contains(userAgent, str);
};
/**
* @param {string} str
* @return {boolean} Whether the user agent contains the given string, ignoring
* case.
*/
goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
var userAgent = goog.labs.userAgent.util.getUserAgent();
return goog.string.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.
*/
goog.labs.userAgent.util.extractVersionTuples = function(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.
var 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');
var data = [];
var 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;
};

View file

@ -0,0 +1,197 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Basic strippable logging definitions.
* @see http://go/closurelogging
*
* @author johnlenz@google.com (John Lenz)
*/
goog.provide('goog.log');
goog.provide('goog.log.Level');
goog.provide('goog.log.LogRecord');
goog.provide('goog.log.Logger');
goog.require('goog.debug');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.LogRecord');
goog.require('goog.debug.Logger');
/** @define {boolean} Whether logging is enabled. */
goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
/** @const {string} */
goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
/**
* @constructor
* @final
*/
goog.log.Logger = goog.debug.Logger;
/**
* @constructor
* @final
*/
goog.log.Level = goog.debug.Logger.Level;
/**
* @constructor
* @final
*/
goog.log.LogRecord = goog.debug.LogRecord;
/**
* Finds or creates a logger for a named subsystem. If a logger has already been
* created with the given name it is returned. Otherwise a new logger is
* created. If a new logger is created its log level will be configured based
* on the goog.debug.LogManager configuration and it will configured to also
* send logging output to its parent's handlers.
* @see goog.debug.LogManager
*
* @param {string} name A name for the logger. This should be a dot-separated
* name and should normally be based on the package name or class name of
* the subsystem, such as goog.net.BrowserChannel.
* @param {goog.log.Level=} opt_level If provided, override the
* default logging level with the provided level.
* @return {goog.log.Logger} The named logger or null if logging is disabled.
*/
goog.log.getLogger = function(name, opt_level) {
if (goog.log.ENABLED) {
var logger = goog.debug.LogManager.getLogger(name);
if (opt_level && logger) {
logger.setLevel(opt_level);
}
return logger;
} else {
return null;
}
};
// TODO(johnlenz): try to tighten the types to these functions.
/**
* Adds a handler to the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {goog.log.Logger} logger
* @param {Function} handler Handler function to add.
*/
goog.log.addHandler = function(logger, handler) {
if (goog.log.ENABLED && logger) {
logger.addHandler(handler);
}
};
/**
* Removes a handler from the logger. This doesn't use the event system because
* we want to be able to add logging to the event system.
* @param {goog.log.Logger} logger
* @param {Function} handler Handler function to remove.
* @return {boolean} Whether the handler was removed.
*/
goog.log.removeHandler = function(logger, handler) {
if (goog.log.ENABLED && logger) {
return logger.removeHandler(handler);
} else {
return false;
}
};
/**
* Logs a message. If the logger is currently enabled for the
* given message level then the given message is forwarded to all the
* registered output Handler objects.
* @param {goog.log.Logger} logger
* @param {goog.log.Level} level One of the level identifiers.
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error|Object=} opt_exception An exception associated with the
* message.
*/
goog.log.log = function(logger, level, msg, opt_exception) {
if (goog.log.ENABLED && logger) {
logger.log(level, msg, opt_exception);
}
};
/**
* Logs a message at the Level.SEVERE level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.log.Logger} logger
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.log.error = function(logger, msg, opt_exception) {
if (goog.log.ENABLED && logger) {
logger.severe(msg, opt_exception);
}
};
/**
* Logs a message at the Level.WARNING level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.log.Logger} logger
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.log.warning = function(logger, msg, opt_exception) {
if (goog.log.ENABLED && logger) {
logger.warning(msg, opt_exception);
}
};
/**
* Logs a message at the Level.INFO level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.log.Logger} logger
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.log.info = function(logger, msg, opt_exception) {
if (goog.log.ENABLED && logger) {
logger.info(msg, opt_exception);
}
};
/**
* Logs a message at the Level.Fine level.
* If the logger is currently enabled for the given message level then the
* given message is forwarded to all the registered output Handler objects.
* @param {goog.log.Logger} logger
* @param {goog.debug.Loggable} msg The message to log.
* @param {Error=} opt_exception An exception associated with the message.
*/
goog.log.fine = function(logger, msg, opt_exception) {
if (goog.log.ENABLED && logger) {
logger.fine(msg, opt_exception);
}
};

View file

@ -0,0 +1,279 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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) {
/**
* X-value
* @type {number}
*/
this.x = goog.isDef(opt_x) ? opt_x : 0;
/**
* Y-value
* @type {number}
*/
this.y = goog.isDef(opt_y) ? 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() {
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() {
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) {
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) {
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 {@code a} and {@code b}.
*/
goog.math.Coordinate.distance = function(a, b) {
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 {@code a}.
*/
goog.math.Coordinate.magnitude = function(a) {
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 {@code a}.
*/
goog.math.Coordinate.azimuth = function(a) {
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 {@code a} and {@code b}.
*/
goog.math.Coordinate.squaredDistance = function(a, b) {
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 {@code a} and {@code b}.
*/
goog.math.Coordinate.difference = function(a, b) {
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) {
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() {
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() {
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() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
};
/**
* Translates this box by the given offsets. If a {@code 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 {@code tx} and {@code 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) {
if (tx instanceof goog.math.Coordinate) {
this.x += tx.x;
this.y += tx.y;
} else {
this.x += Number(tx);
if (goog.isNumber(opt_ty)) {
this.y += opt_ty;
}
}
return this;
};
/**
* Scales this coordinate by the given scale factors. The x and y values are
* scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy}
* is not given, then {@code 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) {
var sy = goog.isNumber(opt_sy) ? 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) {
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) {
this.rotateRadians(goog.math.toRadians(degrees), opt_center);
};

View file

@ -0,0 +1,808 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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');
/**
* 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) {
/**
* @type {!Array<number>}
* @private
*/
this.bits_ = [];
/**
* @type {number}
* @private
*/
this.sign_ = sign;
// 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) {
this.bits_[i] = val;
top = false;
}
}
};
// 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}
* @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) {
if (-128 <= value && value < 128) {
var cachedObj = goog.math.Integer.IntCache_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
goog.math.Integer.IntCache_[value] = obj;
}
return obj;
};
/**
* 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) {
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) {
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) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw 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 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);
/**
* @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() {
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() {
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) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw 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) {
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) {
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() {
return this.sign_;
};
/** @return {boolean} Whether this value is zero. */
goog.math.Integer.prototype.isZero = function() {
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() {
return this.sign_ == -1;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Integer.prototype.isOdd = function() {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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() {
return this.not().add(goog.math.Integer.ONE);
};
/**
* 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) {
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) {
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) {
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) {
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} "this" value divided by the given one.
* @private
*/
goog.math.Integer.prototype.slowDivide_ = function(other) {
if (this.isNegative() || other.isNegative()) {
throw 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);
}
return res;
};
/**
* 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) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Integer.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().divide(other.negate());
} else {
return this.negate().divide(other).negate();
}
} else if (other.isNegative()) {
return this.divide(other.negate()).negate();
}
// 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 res;
};
/**
* 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) {
return this.subtract(this.divide(other).multiply(other));
};
/** @return {!goog.math.Integer} The bitwise-NOT of this value. */
goog.math.Integer.prototype.not = function() {
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) {
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) {
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) {
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) {
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) {
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_);
};

View file

@ -0,0 +1,965 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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.provide('goog.math.Long');
goog.require('goog.asserts');
goog.require('goog.reflect');
/**
* Constructs 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.
*
* @param {number} low The low (signed) 32 bits of the long.
* @param {number} high The high (signed) 32 bits of the long.
* @struct
* @constructor
* @final
*/
goog.math.Long = function(low, high) {
/**
* @type {number}
* @private
*/
this.low_ = low | 0; // force into 32 signed bits.
/**
* @type {number}
* @private
*/
this.high_ = high | 0; // force into 32 signed bits.
};
// 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, !goog.math.Long>}
* @private
*/
goog.math.Long.IntCache_ = {};
/**
* A cache of the Long representations of common values.
* @type {!Object<goog.math.Long.ValueCacheId_, !goog.math.Long>}
* @private
*/
goog.math.Long.valueCache_ = {};
/**
* Returns a cached long number representing the given (32-bit) integer value.
* @param {number} value The 32-bit integer in question.
* @return {!goog.math.Long} The corresponding Long value.
* @private
*/
goog.math.Long.getCachedIntValue_ = function(value) {
return goog.reflect.cache(goog.math.Long.IntCache_, value, function(val) {
return new goog.math.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>}
*/
goog.math.Long.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>}
*/
goog.math.Long.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
];
/**
* Returns a Long representing the given (32-bit) integer value.
* @param {number} value The 32-bit integer in question.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromInt = function(value) {
var intValue = value | 0;
goog.asserts.assert(value === intValue, 'value should be a 32-bit integer');
if (-128 <= intValue && intValue < 128) {
return goog.math.Long.getCachedIntValue_(intValue);
} else {
return new goog.math.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 {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromNumber = function(value) {
if (isNaN(value)) {
return goog.math.Long.getZero();
} else if (value <= -goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.getMinValue();
} else if (value + 1 >= goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.getMaxValue();
} else if (value < 0) {
return goog.math.Long.fromNumber(-value).negate();
} else {
return new goog.math.Long(
(value % goog.math.Long.TWO_PWR_32_DBL_) | 0,
(value / goog.math.Long.TWO_PWR_32_DBL_) | 0);
}
};
/**
* 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 {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromBits = function(lowBits, highBits) {
return new goog.math.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 {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return goog.math.Long.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character: ' + str);
}
// 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.Long.fromNumber(Math.pow(radix, 8));
var result = goog.math.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 = goog.math.Long.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(goog.math.Long.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(goog.math.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.
*/
goog.math.Long.isStringInRange = function(str, opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
var extremeValue = (str.charAt(0) == '-') ?
goog.math.Long.MIN_VALUE_FOR_RADIX_[radix] :
goog.math.Long.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;
}
};
// 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 below.
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_16_DBL_ = 1 << 16;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_32_DBL_ =
goog.math.Long.TWO_PWR_16_DBL_ * goog.math.Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_64_DBL_ =
goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_32_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_63_DBL_ = goog.math.Long.TWO_PWR_64_DBL_ / 2;
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getZero = function() {
return goog.math.Long.getCachedIntValue_(0);
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getOne = function() {
return goog.math.Long.getCachedIntValue_(1);
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getNegOne = function() {
return goog.math.Long.getCachedIntValue_(-1);
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getMaxValue = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MAX_VALUE,
function() {
return goog.math.Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0);
});
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getMinValue = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MIN_VALUE,
function() { return goog.math.Long.fromBits(0, 0x80000000 | 0); });
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getTwoPwr24 = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.TWO_PWR_24,
function() { return goog.math.Long.fromInt(1 << 24); });
};
/** @return {number} The value, assuming it is a 32-bit integer. */
goog.math.Long.prototype.toInt = function() {
return this.low_;
};
/** @return {number} The closest floating-point representation to this value. */
goog.math.Long.prototype.toNumber = function() {
return this.high_ * goog.math.Long.TWO_PWR_32_DBL_ +
this.getLowBitsUnsigned();
};
/**
* @param {number=} opt_radix The radix in which the text should be written.
* @return {string} The textual representation of this value.
* @override
*/
goog.math.Long.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
}
if (this.isNegative()) {
if (this.equals(goog.math.Long.getMinValue())) {
// We need to change the Long value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixLong = goog.math.Long.fromNumber(radix);
var div = this.div(radixLong);
var rem = div.multiply(radixLong).subtract(this);
return div.toString(radix) + rem.toInt().toString(radix);
} else {
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.Long.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.div(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;
}
}
};
/** @return {number} The high 32-bits as a signed value. */
goog.math.Long.prototype.getHighBits = function() {
return this.high_;
};
/** @return {number} The low 32-bits as a signed value. */
goog.math.Long.prototype.getLowBits = function() {
return this.low_;
};
/** @return {number} The low 32-bits as an unsigned value. */
goog.math.Long.prototype.getLowBitsUnsigned = function() {
return (this.low_ >= 0) ? this.low_ :
goog.math.Long.TWO_PWR_32_DBL_ + this.low_;
};
/**
* @return {number} Returns the number of bits needed to represent the absolute
* value of this Long.
*/
goog.math.Long.prototype.getNumBitsAbs = function() {
if (this.isNegative()) {
if (this.equals(goog.math.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. */
goog.math.Long.prototype.isZero = function() {
return this.high_ == 0 && this.low_ == 0;
};
/** @return {boolean} Whether this value is negative. */
goog.math.Long.prototype.isNegative = function() {
return this.high_ < 0;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Long.prototype.isOdd = function() {
return (this.low_ & 1) == 1;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long equals the other.
*/
goog.math.Long.prototype.equals = function(other) {
return (this.high_ == other.high_) && (this.low_ == other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long does not equal the other.
*/
goog.math.Long.prototype.notEquals = function(other) {
return (this.high_ != other.high_) || (this.low_ != other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than the other.
*/
goog.math.Long.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than or equal to the other.
*/
goog.math.Long.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than the other.
*/
goog.math.Long.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than or equal to the other.
*/
goog.math.Long.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* Compares this Long with the given one.
* @param {goog.math.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.
*/
goog.math.Long.prototype.compare = function(other) {
if (this.equals(other)) {
return 0;
}
var thisNeg = this.isNegative();
var otherNeg = other.isNegative();
if (thisNeg && !otherNeg) {
return -1;
}
if (!thisNeg && otherNeg) {
return 1;
}
// at this point, the signs are the same, so subtraction will not overflow
if (this.subtract(other).isNegative()) {
return -1;
} else {
return 1;
}
};
/** @return {!goog.math.Long} The negation of this value. */
goog.math.Long.prototype.negate = function() {
if (this.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getMinValue();
} else {
return this.not().add(goog.math.Long.getOne());
}
};
/**
* Returns the sum of this and the given Long.
* @param {goog.math.Long} other Long to add to this one.
* @return {!goog.math.Long} The sum of this and the given Long.
*/
goog.math.Long.prototype.add = function(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 goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns the difference of this and the given Long.
* @param {goog.math.Long} other Long to subtract from this.
* @return {!goog.math.Long} The difference of this and the given Long.
*/
goog.math.Long.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given long.
* @param {goog.math.Long} other Long to multiply with this.
* @return {!goog.math.Long} The product of this and the other.
*/
goog.math.Long.prototype.multiply = function(other) {
if (this.isZero()) {
return goog.math.Long.getZero();
} else if (other.isZero()) {
return goog.math.Long.getZero();
}
if (this.equals(goog.math.Long.getMinValue())) {
return other.isOdd() ? goog.math.Long.getMinValue() :
goog.math.Long.getZero();
} else if (other.equals(goog.math.Long.getMinValue())) {
return this.isOdd() ? goog.math.Long.getMinValue() :
goog.math.Long.getZero();
}
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 longs are small, use float multiplication
if (this.lessThan(goog.math.Long.getTwoPwr24()) &&
other.lessThan(goog.math.Long.getTwoPwr24())) {
return goog.math.Long.fromNumber(this.toNumber() * other.toNumber());
}
// 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 goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns this Long divided by the given one.
* @param {goog.math.Long} other Long by which to divide.
* @return {!goog.math.Long} This Long divided by the given one.
*/
goog.math.Long.prototype.div = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Long.getZero();
}
if (this.equals(goog.math.Long.getMinValue())) {
if (other.equals(goog.math.Long.getOne()) ||
other.equals(goog.math.Long.getNegOne())) {
return goog.math.Long.getMinValue(); // recall -MIN_VALUE == MIN_VALUE
} else if (other.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getOne();
} else {
// 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(goog.math.Long.getZero())) {
return other.isNegative() ? goog.math.Long.getOne() :
goog.math.Long.getNegOne();
} else {
var rem = this.subtract(other.multiply(approx));
var result = approx.add(rem.div(other));
return result;
}
}
} else if (other.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getZero();
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().div(other.negate());
} else {
return this.negate().div(other).negate();
}
} else if (other.isNegative()) {
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 = goog.math.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 = goog.math.Long.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = goog.math.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 = goog.math.Long.getOne();
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Long modulo the given one.
* @param {goog.math.Long} other Long by which to mod.
* @return {!goog.math.Long} This Long modulo the given one.
*/
goog.math.Long.prototype.modulo = function(other) {
return this.subtract(this.div(other).multiply(other));
};
/** @return {!goog.math.Long} The bitwise-NOT of this value. */
goog.math.Long.prototype.not = function() {
return goog.math.Long.fromBits(~this.low_, ~this.high_);
};
/**
* Returns the bitwise-AND of this Long and the given one.
* @param {goog.math.Long} other The Long with which to AND.
* @return {!goog.math.Long} The bitwise-AND of this and the other.
*/
goog.math.Long.prototype.and = function(other) {
return goog.math.Long.fromBits(
this.low_ & other.low_, this.high_ & other.high_);
};
/**
* Returns the bitwise-OR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to OR.
* @return {!goog.math.Long} The bitwise-OR of this and the other.
*/
goog.math.Long.prototype.or = function(other) {
return goog.math.Long.fromBits(
this.low_ | other.low_, this.high_ | other.high_);
};
/**
* Returns the bitwise-XOR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to XOR.
* @return {!goog.math.Long} The bitwise-XOR of this and the other.
*/
goog.math.Long.prototype.xor = function(other) {
return goog.math.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 {!goog.math.Long} This shifted to the left by the given amount.
*/
goog.math.Long.prototype.shiftLeft = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var low = this.low_;
if (numBits < 32) {
var high = this.high_;
return goog.math.Long.fromBits(
low << numBits, (high << numBits) | (low >>> (32 - numBits)));
} else {
return goog.math.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 {!goog.math.Long} This shifted to the right by the given amount.
*/
goog.math.Long.prototype.shiftRight = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)), high >> numBits);
} else {
return goog.math.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 {!goog.math.Long} This shifted to the right by the given amount, with
* zeros placed into the new leading bits.
*/
goog.math.Long.prototype.shiftRightUnsigned = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)), high >>> numBits);
} else if (numBits == 32) {
return goog.math.Long.fromBits(high, 0);
} else {
return goog.math.Long.fromBits(high >>> (numBits - 32), 0);
}
}
};
/**
* @enum {number} Ids of commonly requested Long instances.
* @private
*/
goog.math.Long.ValueCacheId_ = {
MAX_VALUE: 1,
MIN_VALUE: 2,
TWO_PWR_24: 6
};

View file

@ -0,0 +1,448 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Additional mathematical functions.
*/
goog.provide('goog.math');
goog.require('goog.array');
goog.require('goog.asserts');
/**
* Returns a random integer greater than or equal to 0 and less than {@code 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) {
return Math.floor(Math.random() * a);
};
/**
* Returns a random number greater than or equal to {@code a} and less than
* {@code 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) {
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) {
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) {
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) {
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 {@code a} and {@code b} are nearly equal.
*/
goog.math.nearlyEquals = function(a, b, opt_tolerance) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
var compare = opt_compareFn || function(a, b) { return a == b; };
var collect = opt_collectorFn || function(i1, i2) { 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,
* {@code NaN} if any of the arguments is not a valid number).
*/
goog.math.sum = function(var_args) {
return /** @type {number} */ (
goog.array.reduce(
arguments, function(sum, value) { 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 ({@code NaN} if no arguments
* were provided or any of the arguments is not a valid number).
*/
goog.math.average = function(var_args) {
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 {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.sampleVariance = function(var_args) {
var sampleSize = arguments.length;
if (sampleSize < 2) {
return 0;
}
var mean = goog.math.average.apply(null, arguments);
var variance =
goog.math.sum.apply(null, goog.array.map(arguments, function(val) {
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 {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.standardDeviation = function(var_args) {
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 {@code num} is an integer.
*/
goog.math.isInt = function(num) {
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 {@code num} is a finite number.
* @deprecated Use {@link isFinite} instead.
*/
goog.math.isFiniteNumber = function(num) {
return isFinite(num);
};
/**
* @param {number} num The number to test.
* @return {boolean} Whether it is negative zero.
*/
goog.math.isNegativeZero = function(num) {
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) {
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 {@code 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 {@code num}.
*/
goog.math.safeFloor = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.floor(num + (opt_epsilon || 2e-15));
};
/**
* A tweaked variant of {@code Math.ceil}. See {@code 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 {@code num}.
*/
goog.math.safeCeil = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.ceil(num - (opt_epsilon || 2e-15));
};

View file

@ -0,0 +1,227 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional sizes.
* @author brenneman@google.com (Shawn Brenneman)
*/
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) {
/**
* 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) {
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() {
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() {
return '(' + this.width + ' x ' + this.height + ')';
};
}
/**
* @return {number} The longer of the two dimensions in the size.
*/
goog.math.Size.prototype.getLongest = function() {
return Math.max(this.width, this.height);
};
/**
* @return {number} The shorter of the two dimensions in the size.
*/
goog.math.Size.prototype.getShortest = function() {
return Math.min(this.width, this.height);
};
/**
* @return {number} The area of the size (width * height).
*/
goog.math.Size.prototype.area = function() {
return this.width * this.height;
};
/**
* @return {number} The perimeter of the size (width + height) * 2.
*/
goog.math.Size.prototype.perimeter = function() {
return (this.width + this.height) * 2;
};
/**
* @return {number} The ratio of the size's width to its height.
*/
goog.math.Size.prototype.aspectRatio = function() {
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() {
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() {
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) {
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() {
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() {
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 {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not
* given, then {@code 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) {
var sy = goog.isNumber(opt_sy) ? 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) {
var 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) {
var s = this.aspectRatio() > target.aspectRatio() ?
target.width / this.width :
target.height / this.height;
return this.scale(s);
};

View file

@ -0,0 +1,945 @@
// Copyright 2007 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors. All Rights
// Reserved.
/**
* @license Portions of this code are from MochiKit, received by
* The Closure Authors under the MIT license. All other code is Copyright
* 2005-2009 The Closure Authors. All Rights Reserved.
*/
/**
* @fileoverview Classes for tracking asynchronous operations and handling the
* results. The Deferred object here is patterned after the Deferred object in
* the Twisted python networking framework.
*
* See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
*
* Based on the Dojo code which in turn is based on the MochiKit code.
*
* @author arv@google.com (Erik Arvidsson)
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.async.Deferred');
goog.provide('goog.async.Deferred.AlreadyCalledError');
goog.provide('goog.async.Deferred.CanceledError');
goog.require('goog.Promise');
goog.require('goog.Thenable');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');
/**
* A Deferred represents the result of an asynchronous operation. A Deferred
* instance has no result when it is created, and is "fired" (given an initial
* result) by calling {@code callback} or {@code errback}.
*
* Once fired, the result is passed through a sequence of callback functions
* registered with {@code addCallback} or {@code addErrback}. The functions may
* mutate the result before it is passed to the next function in the sequence.
*
* Callbacks and errbacks may be added at any time, including after the Deferred
* has been "fired". If there are no pending actions in the execution sequence
* of a fired Deferred, any new callback functions will be called with the last
* computed result. Adding a callback function is the only way to access the
* result of the Deferred.
*
* If a Deferred operation is canceled, an optional user-provided cancellation
* function is invoked which may perform any special cleanup, followed by firing
* the Deferred's errback sequence with a {@code CanceledError}. If the
* Deferred has already fired, cancellation is ignored.
*
* Deferreds may be templated to a specific type they produce using generics
* with syntax such as:
*
* /** @type {goog.async.Deferred<string>} *\
* var d = new goog.async.Deferred();
* // Compiler can infer that foo is a string.
* d.addCallback(function(foo) {...});
* d.callback('string'); // Checked to be passed a string
*
* Since deferreds are often used to produce different values across a chain,
* the type information is not propagated across chains, but rather only
* associated with specifically cast objects.
*
* @param {Function=} opt_onCancelFunction A function that will be called if the
* Deferred is canceled. If provided, this function runs before the
* Deferred is fired with a {@code CanceledError}.
* @param {Object=} opt_defaultScope The default object context to call
* callbacks and errbacks in.
* @constructor
* @implements {goog.Thenable<VALUE>}
* @template VALUE
*/
goog.async.Deferred = function(opt_onCancelFunction, opt_defaultScope) {
/**
* Entries in the sequence are arrays containing a callback, an errback, and
* an optional scope. The callback or errback in an entry may be null.
* @type {!Array<!Array>}
* @private
*/
this.sequence_ = [];
/**
* Optional function that will be called if the Deferred is canceled.
* @type {Function|undefined}
* @private
*/
this.onCancelFunction_ = opt_onCancelFunction;
/**
* The default scope to execute callbacks and errbacks in.
* @type {Object}
* @private
*/
this.defaultScope_ = opt_defaultScope || null;
/**
* Whether the Deferred has been fired.
* @type {boolean}
* @private
*/
this.fired_ = false;
/**
* Whether the last result in the execution sequence was an error.
* @type {boolean}
* @private
*/
this.hadError_ = false;
/**
* The current Deferred result, updated as callbacks and errbacks are
* executed.
* @type {*}
* @private
*/
this.result_ = undefined;
/**
* Whether the Deferred is blocked waiting on another Deferred to fire. If a
* callback or errback returns a Deferred as a result, the execution sequence
* is blocked until that Deferred result becomes available.
* @type {boolean}
* @private
*/
this.blocked_ = false;
/**
* Whether this Deferred is blocking execution of another Deferred. If this
* instance was returned as a result in another Deferred's execution
* sequence,that other Deferred becomes blocked until this instance's
* execution sequence completes. No additional callbacks may be added to a
* Deferred once it is blocking another instance.
* @type {boolean}
* @private
*/
this.blocking_ = false;
/**
* Whether the Deferred has been canceled without having a custom cancel
* function.
* @type {boolean}
* @private
*/
this.silentlyCanceled_ = false;
/**
* If an error is thrown during Deferred execution with no errback to catch
* it, the error is rethrown after a timeout. Reporting the error after a
* timeout allows execution to continue in the calling context (empty when
* no error is scheduled).
* @type {number}
* @private
*/
this.unhandledErrorId_ = 0;
/**
* If this Deferred was created by branch(), this will be the "parent"
* Deferred.
* @type {goog.async.Deferred}
* @private
*/
this.parent_ = null;
/**
* The number of Deferred objects that have been branched off this one. This
* will be decremented whenever a branch is fired or canceled.
* @type {number}
* @private
*/
this.branches_ = 0;
if (goog.async.Deferred.LONG_STACK_TRACES) {
/**
* Holds the stack trace at time of deferred creation if the JS engine
* provides the Error.captureStackTrace API.
* @private {?string}
*/
this.constructorStack_ = null;
if (Error.captureStackTrace) {
var target = { stack: '' };
Error.captureStackTrace(target, goog.async.Deferred);
// Check if Error.captureStackTrace worked. It fails in gjstest.
if (typeof target.stack == 'string') {
// Remove first line and force stringify to prevent memory leak due to
// holding on to actual stack frames.
this.constructorStack_ = target.stack.replace(/^[^\n]*\n/, '');
}
}
}
};
/**
* @define {boolean} Whether unhandled errors should always get rethrown to the
* global scope. Defaults to the value of goog.DEBUG.
*/
goog.define('goog.async.Deferred.STRICT_ERRORS', false);
/**
* @define {boolean} Whether to attempt to make stack traces long. Defaults to
* the value of goog.DEBUG.
*/
goog.define('goog.async.Deferred.LONG_STACK_TRACES', false);
/**
* Cancels a Deferred that has not yet been fired, or is blocked on another
* deferred operation. If this Deferred is waiting for a blocking Deferred to
* fire, the blocking Deferred will also be canceled.
*
* If this Deferred was created by calling branch() on a parent Deferred with
* opt_propagateCancel set to true, the parent may also be canceled. If
* opt_deepCancel is set, cancel() will be called on the parent (as well as any
* other ancestors if the parent is also a branch). If one or more branches were
* created with opt_propagateCancel set to true, the parent will be canceled if
* cancel() is called on all of those branches.
*
* @param {boolean=} opt_deepCancel If true, cancels this Deferred's parent even
* if cancel() hasn't been called on some of the parent's branches. Has no
* effect on a branch without opt_propagateCancel set to true.
*/
goog.async.Deferred.prototype.cancel = function(opt_deepCancel) {
if (!this.hasFired()) {
if (this.parent_) {
// Get rid of the parent reference before potentially running the parent's
// canceler function to ensure that this cancellation isn't
// double-counted.
var parent = this.parent_;
delete this.parent_;
if (opt_deepCancel) {
parent.cancel(opt_deepCancel);
} else {
parent.branchCancel_();
}
}
if (this.onCancelFunction_) {
// Call in user-specified scope.
this.onCancelFunction_.call(this.defaultScope_, this);
} else {
this.silentlyCanceled_ = true;
}
if (!this.hasFired()) {
this.errback(new goog.async.Deferred.CanceledError(this));
}
} else if (this.result_ instanceof goog.async.Deferred) {
this.result_.cancel();
}
};
/**
* Handle a single branch being canceled. Once all branches are canceled, this
* Deferred will be canceled as well.
*
* @private
*/
goog.async.Deferred.prototype.branchCancel_ = function() {
this.branches_--;
if (this.branches_ <= 0) {
this.cancel();
}
};
/**
* Called after a blocking Deferred fires. Unblocks this Deferred and resumes
* its execution sequence.
*
* @param {boolean} isSuccess Whether the result is a success or an error.
* @param {*} res The result of the blocking Deferred.
* @private
*/
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
this.blocked_ = false;
this.updateResult_(isSuccess, res);
};
/**
* Updates the current result based on the success or failure of the last action
* in the execution sequence.
*
* @param {boolean} isSuccess Whether the new result is a success or an error.
* @param {*} res The result.
* @private
*/
goog.async.Deferred.prototype.updateResult_ = function(isSuccess, res) {
this.fired_ = true;
this.result_ = res;
this.hadError_ = !isSuccess;
this.fire_();
};
/**
* Verifies that the Deferred has not yet been fired.
*
* @private
* @throws {Error} If this has already been fired.
*/
goog.async.Deferred.prototype.check_ = function() {
if (this.hasFired()) {
if (!this.silentlyCanceled_) {
throw new goog.async.Deferred.AlreadyCalledError(this);
}
this.silentlyCanceled_ = false;
}
};
/**
* Fire the execution sequence for this Deferred by passing the starting result
* to the first registered callback.
* @param {VALUE=} opt_result The starting result.
*/
goog.async.Deferred.prototype.callback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.updateResult_(true /* isSuccess */, opt_result);
};
/**
* Fire the execution sequence for this Deferred by passing the starting error
* result to the first registered errback.
* @param {*=} opt_result The starting error.
*/
goog.async.Deferred.prototype.errback = function(opt_result) {
this.check_();
this.assertNotDeferred_(opt_result);
this.makeStackTraceLong_(opt_result);
this.updateResult_(false /* isSuccess */, opt_result);
};
/**
* Attempt to make the error's stack trace be long in that it contains the
* stack trace from the point where the deferred was created on top of the
* current stack trace to give additional context.
* @param {*} error
* @private
*/
goog.async.Deferred.prototype.makeStackTraceLong_ = function(error) {
if (!goog.async.Deferred.LONG_STACK_TRACES) {
return;
}
if (this.constructorStack_ && goog.isObject(error) && error.stack &&
// Stack looks like it was system generated. See
// https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
(/^[^\n]+(\n [^\n]+)+/).test(error.stack)) {
error.stack = error.stack + '\nDEFERRED OPERATION:\n' +
this.constructorStack_;
}
};
/**
* Asserts that an object is not a Deferred.
* @param {*} obj The object to test.
* @throws {Error} Throws an exception if the object is a Deferred.
* @private
*/
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
goog.asserts.assert(
!(obj instanceof goog.async.Deferred),
'An execution sequence may not be initiated with a blocking Deferred.');
};
/**
* Register a callback function to be called with a successful result. If no
* value is returned by the callback function, the result value is unchanged. If
* a new value is returned, it becomes the Deferred result and will be passed to
* the next callback in the execution sequence.
*
* If the function throws an error, the error becomes the new result and will be
* passed to the next errback in the execution chain.
*
* If the function returns a Deferred, the execution sequence will be blocked
* until that Deferred fires. Its result will be passed to the next callback (or
* errback if it is an error result) in this Deferred's execution sequence.
*
* @param {function(this:T,VALUE):?} cb The function to be called with a
* successful result.
* @param {T=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
return this.addCallbacks(cb, null, opt_scope);
};
/**
* Register a callback function to be called with an error result. If no value
* is returned by the function, the error result is unchanged. If a new error
* value is returned or thrown, that error becomes the Deferred result and will
* be passed to the next errback in the execution sequence.
*
* If the errback function handles the error by returning a non-error value,
* that result will be passed to the next normal callback in the sequence.
*
* If the function returns a Deferred, the execution sequence will be blocked
* until that Deferred fires. Its result will be passed to the next callback (or
* errback if it is an error result) in this Deferred's execution sequence.
*
* @param {function(this:T,?):?} eb The function to be called on an
* unsuccessful result.
* @param {T=} opt_scope An optional scope to call the errback in.
* @return {!goog.async.Deferred<VALUE>} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
return this.addCallbacks(null, eb, opt_scope);
};
/**
* Registers one function as both a callback and errback.
*
* @param {function(this:T,?):?} f The function to be called on any result.
* @param {T=} opt_scope An optional scope to call the function in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
return this.addCallbacks(f, f, opt_scope);
};
/**
* Like addBoth, but propagates uncaught exceptions in the errback.
*
* @param {function(this:T,?):?} f The function to be called on any result.
* @param {T=} opt_scope An optional scope to call the function in.
* @return {!goog.async.Deferred<VALUE>} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addFinally = function(f, opt_scope) {
return this.addCallbacks(f, function(err) {
var result = f.call(/** @type {?} */ (this), err);
if (!goog.isDef(result)) {
throw err;
}
return result;
}, opt_scope);
};
/**
* Registers a callback function and an errback function at the same position
* in the execution sequence. Only one of these functions will execute,
* depending on the error state during the execution sequence.
*
* NOTE: This is not equivalent to {@code def.addCallback().addErrback()}! If
* the callback is invoked, the errback will be skipped, and vice versa.
*
* @param {?(function(this:T,VALUE):?)} cb The function to be called on a
* successful result.
* @param {?(function(this:T,?):?)} eb The function to be called on an
* unsuccessful result.
* @param {T=} opt_scope An optional scope to call the functions in.
* @return {!goog.async.Deferred} This Deferred.
* @template T
*/
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
goog.asserts.assert(!this.blocking_, 'Blocking Deferreds can not be re-used');
this.sequence_.push([cb, eb, opt_scope]);
if (this.hasFired()) {
this.fire_();
}
return this;
};
/**
* Implements {@see goog.Thenable} for seamless integration with
* {@see goog.Promise}.
* Deferred results are mutable and may represent multiple values over
* their lifetime. Calling {@code then} on a Deferred returns a Promise
* with the result of the Deferred at that point in its callback chain.
* Note that if the Deferred result is never mutated, and only
* {@code then} calls are made, the Deferred will behave like a Promise.
*
* @override
*/
goog.async.Deferred.prototype.then = function(opt_onFulfilled, opt_onRejected,
opt_context) {
var resolve, reject;
var promise = new goog.Promise(function(res, rej) {
// Copying resolvers to outer scope, so that they are available when the
// deferred callback fires (which may be synchronous).
resolve = res;
reject = rej;
});
this.addCallbacks(resolve, function(reason) {
if (reason instanceof goog.async.Deferred.CanceledError) {
promise.cancel();
} else {
reject(reason);
}
});
return promise.then(opt_onFulfilled, opt_onRejected, opt_context);
};
goog.Thenable.addImplementation(goog.async.Deferred);
/**
* Links another Deferred to the end of this Deferred's execution sequence. The
* result of this execution sequence will be passed as the starting result for
* the chained Deferred, invoking either its first callback or errback.
*
* @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
* @return {!goog.async.Deferred} This Deferred.
*/
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
this.addCallbacks(
otherDeferred.callback, otherDeferred.errback, otherDeferred);
return this;
};
/**
* Makes this Deferred wait for another Deferred's execution sequence to
* complete before continuing.
*
* This is equivalent to adding a callback that returns {@code otherDeferred},
* but doesn't prevent additional callbacks from being added to
* {@code otherDeferred}.
*
* @param {!goog.async.Deferred|!goog.Thenable} otherDeferred The Deferred
* to wait for.
* @return {!goog.async.Deferred} This Deferred.
*/
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
if (!(otherDeferred instanceof goog.async.Deferred)) {
// The Thenable case.
return this.addCallback(function() {
return otherDeferred;
});
}
return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};
/**
* Creates a branch off this Deferred's execution sequence, and returns it as a
* new Deferred. The branched Deferred's starting result will be shared with the
* parent at the point of the branch, even if further callbacks are added to the
* parent.
*
* All branches at the same stage in the execution sequence will receive the
* same starting value.
*
* @param {boolean=} opt_propagateCancel If cancel() is called on every child
* branch created with opt_propagateCancel, the parent will be canceled as
* well.
* @return {!goog.async.Deferred<VALUE>} A Deferred that will be started with
* the computed result from this stage in the execution sequence.
*/
goog.async.Deferred.prototype.branch = function(opt_propagateCancel) {
var d = new goog.async.Deferred();
this.chainDeferred(d);
if (opt_propagateCancel) {
d.parent_ = this;
this.branches_++;
}
return d;
};
/**
* @return {boolean} Whether the execution sequence has been started on this
* Deferred by invoking {@code callback} or {@code errback}.
*/
goog.async.Deferred.prototype.hasFired = function() {
return this.fired_;
};
/**
* @param {*} res The latest result in the execution sequence.
* @return {boolean} Whether the current result is an error that should cause
* the next errback to fire. May be overridden by subclasses to handle
* special error types.
* @protected
*/
goog.async.Deferred.prototype.isError = function(res) {
return res instanceof Error;
};
/**
* @return {boolean} Whether an errback exists in the remaining sequence.
* @private
*/
goog.async.Deferred.prototype.hasErrback_ = function() {
return goog.array.some(this.sequence_, function(sequenceRow) {
// The errback is the second element in the array.
return goog.isFunction(sequenceRow[1]);
});
};
/**
* Exhausts the execution sequence while a result is available. The result may
* be modified by callbacks or errbacks, and execution will block if the
* returned result is an incomplete Deferred.
*
* @private
*/
goog.async.Deferred.prototype.fire_ = function() {
if (this.unhandledErrorId_ && this.hasFired() && this.hasErrback_()) {
// It is possible to add errbacks after the Deferred has fired. If a new
// errback is added immediately after the Deferred encountered an unhandled
// error, but before that error is rethrown, the error is unscheduled.
goog.async.Deferred.unscheduleError_(this.unhandledErrorId_);
this.unhandledErrorId_ = 0;
}
if (this.parent_) {
this.parent_.branches_--;
delete this.parent_;
}
var res = this.result_;
var unhandledException = false;
var isNewlyBlocked = false;
while (this.sequence_.length && !this.blocked_) {
var sequenceEntry = this.sequence_.shift();
var callback = sequenceEntry[0];
var errback = sequenceEntry[1];
var scope = sequenceEntry[2];
var f = this.hadError_ ? errback : callback;
if (f) {
try {
var ret = f.call(scope || this.defaultScope_, res);
// If no result, then use previous result.
if (goog.isDef(ret)) {
// Bubble up the error as long as the return value hasn't changed.
this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
this.result_ = res = ret;
}
if (goog.Thenable.isImplementedBy(res) ||
(typeof goog.global['Promise'] === 'function' &&
res instanceof goog.global['Promise'])) {
isNewlyBlocked = true;
this.blocked_ = true;
}
} catch (ex) {
res = ex;
this.hadError_ = true;
this.makeStackTraceLong_(res);
if (!this.hasErrback_()) {
// If an error is thrown with no additional errbacks in the queue,
// prepare to rethrow the error.
unhandledException = true;
}
}
}
}
this.result_ = res;
if (isNewlyBlocked) {
var onCallback = goog.bind(this.continue_, this, true /* isSuccess */);
var onErrback = goog.bind(this.continue_, this, false /* isSuccess */);
if (res instanceof goog.async.Deferred) {
res.addCallbacks(onCallback, onErrback);
res.blocking_ = true;
} else {
res.then(onCallback, onErrback);
}
} else if (goog.async.Deferred.STRICT_ERRORS && this.isError(res) &&
!(res instanceof goog.async.Deferred.CanceledError)) {
this.hadError_ = true;
unhandledException = true;
}
if (unhandledException) {
// Rethrow the unhandled error after a timeout. Execution will continue, but
// the error will be seen by global handlers and the user. The throw will
// be canceled if another errback is appended before the timeout executes.
// The error's original stack trace is preserved where available.
this.unhandledErrorId_ = goog.async.Deferred.scheduleError_(res);
}
};
/**
* Creates a Deferred that has an initial result.
*
* @param {*=} opt_result The result.
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.succeed = function(opt_result) {
var d = new goog.async.Deferred();
d.callback(opt_result);
return d;
};
/**
* Creates a Deferred that fires when the given promise resolves.
* Use only during migration to Promises.
*
* @param {!goog.Promise<T>} promise
* @return {!goog.async.Deferred<T>} The new Deferred.
* @template T
*/
goog.async.Deferred.fromPromise = function(promise) {
var d = new goog.async.Deferred();
d.callback();
d.addCallback(function() {
return promise;
});
return d;
};
/**
* Creates a Deferred that has an initial error result.
*
* @param {*} res The error result.
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.fail = function(res) {
var d = new goog.async.Deferred();
d.errback(res);
return d;
};
/**
* Creates a Deferred that has already been canceled.
*
* @return {!goog.async.Deferred} The new Deferred.
*/
goog.async.Deferred.canceled = function() {
var d = new goog.async.Deferred();
d.cancel();
return d;
};
/**
* Normalizes values that may or may not be Deferreds.
*
* If the input value is a Deferred, the Deferred is branched (so the original
* execution sequence is not modified) and the input callback added to the new
* branch. The branch is returned to the caller.
*
* If the input value is not a Deferred, the callback will be executed
* immediately and an already firing Deferred will be returned to the caller.
*
* In the following (contrived) example, if <code>isImmediate</code> is true
* then 3 is alerted immediately, otherwise 6 is alerted after a 2-second delay.
*
* <pre>
* var value;
* if (isImmediate) {
* value = 3;
* } else {
* value = new goog.async.Deferred();
* setTimeout(function() { value.callback(6); }, 2000);
* }
*
* var d = goog.async.Deferred.when(value, alert);
* </pre>
*
* @param {*} value Deferred or normal value to pass to the callback.
* @param {function(this:T, ?):?} callback The callback to execute.
* @param {T=} opt_scope An optional scope to call the callback in.
* @return {!goog.async.Deferred} A new Deferred that will call the input
* callback with the input value.
* @template T
*/
goog.async.Deferred.when = function(value, callback, opt_scope) {
if (value instanceof goog.async.Deferred) {
return value.branch(true).addCallback(callback, opt_scope);
} else {
return goog.async.Deferred.succeed(value).addCallback(callback, opt_scope);
}
};
/**
* An error sub class that is used when a Deferred has already been called.
* @param {!goog.async.Deferred} deferred The Deferred.
*
* @constructor
* @extends {goog.debug.Error}
*/
goog.async.Deferred.AlreadyCalledError = function(deferred) {
goog.debug.Error.call(this);
/**
* The Deferred that raised this error.
* @type {goog.async.Deferred}
*/
this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);
/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.message =
'Deferred has already fired';
/** @override */
goog.async.Deferred.AlreadyCalledError.prototype.name = 'AlreadyCalledError';
/**
* An error sub class that is used when a Deferred is canceled.
*
* @param {!goog.async.Deferred} deferred The Deferred object.
* @constructor
* @extends {goog.debug.Error}
*/
goog.async.Deferred.CanceledError = function(deferred) {
goog.debug.Error.call(this);
/**
* The Deferred that raised this error.
* @type {goog.async.Deferred}
*/
this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.CanceledError, goog.debug.Error);
/** @override */
goog.async.Deferred.CanceledError.prototype.message = 'Deferred was canceled';
/** @override */
goog.async.Deferred.CanceledError.prototype.name = 'CanceledError';
/**
* Wrapper around errors that are scheduled to be thrown by failing deferreds
* after a timeout.
*
* @param {*} error Error from a failing deferred.
* @constructor
* @final
* @private
* @struct
*/
goog.async.Deferred.Error_ = function(error) {
/** @const @private {number} */
this.id_ = goog.global.setTimeout(goog.bind(this.throwError, this), 0);
/** @const @private {*} */
this.error_ = error;
};
/**
* Actually throws the error and removes it from the list of pending
* deferred errors.
*/
goog.async.Deferred.Error_.prototype.throwError = function() {
goog.asserts.assert(goog.async.Deferred.errorMap_[this.id_],
'Cannot throw an error that is not scheduled.');
delete goog.async.Deferred.errorMap_[this.id_];
throw this.error_;
};
/**
* Resets the error throw timer.
*/
goog.async.Deferred.Error_.prototype.resetTimer = function() {
goog.global.clearTimeout(this.id_);
};
/**
* Map of unhandled errors scheduled to be rethrown in a future timestep.
* @private {!Object<(number|string), goog.async.Deferred.Error_>}
*/
goog.async.Deferred.errorMap_ = {};
/**
* Schedules an error to be thrown after a delay.
* @param {*} error Error from a failing deferred.
* @return {number} Id of the error.
* @private
*/
goog.async.Deferred.scheduleError_ = function(error) {
var deferredError = new goog.async.Deferred.Error_(error);
goog.async.Deferred.errorMap_[deferredError.id_] = deferredError;
return deferredError.id_;
};
/**
* Unschedules an error from being thrown.
* @param {number} id Id of the deferred error to unschedule.
* @private
*/
goog.async.Deferred.unscheduleError_ = function(id) {
var error = goog.async.Deferred.errorMap_[id];
if (error) {
error.resetTimer();
delete goog.async.Deferred.errorMap_[id];
}
};
/**
* Asserts that there are no pending deferred errors. If there are any
* scheduled errors, one will be thrown immediately to make this function fail.
*/
goog.async.Deferred.assertNoErrors = function() {
var map = goog.async.Deferred.errorMap_;
for (var key in map) {
var error = map[key];
error.resetTimer();
error.throwError();
}
};

View file

@ -0,0 +1,130 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Error 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) {
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';
}
};

View file

@ -0,0 +1,42 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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'
};

View file

@ -0,0 +1,116 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Constants for HTTP status codes.
*/
goog.provide('goog.net.HttpStatus');
/**
* HTTP Status Codes defined in RFC 2616 and RFC 6585.
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
* @see http://tools.ietf.org/html/rfc6585
* @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,
// Redirection 3xx
MULTIPLE_CHOICES: 300,
MOVED_PERMANENTLY: 301,
FOUND: 302,
SEE_OTHER: 303,
NOT_MODIFIED: 304,
USE_PROXY: 305,
TEMPORARY_REDIRECT: 307,
// 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,
PRECONDITION_REQUIRED: 428,
TOO_MANY_REQUESTS: 429,
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
// Server Error 5xx
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504,
HTTP_VERSION_NOT_SUPPORTED: 505,
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) {
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;
}
};

View file

@ -0,0 +1,393 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility to load JavaScript files via DOM script tags.
* Refactored from goog.net.Jsonp. Works cross-domain.
*
*/
goog.provide('goog.net.jsloader');
goog.provide('goog.net.jsloader.Error');
goog.provide('goog.net.jsloader.ErrorCode');
goog.provide('goog.net.jsloader.Options');
goog.require('goog.array');
goog.require('goog.async.Deferred');
goog.require('goog.debug.Error');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.safe');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.object');
/**
* The name of the property of goog.global under which the JavaScript
* verification object is stored by the loaded script.
* @private {string}
*/
goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
/**
* The default length of time, in milliseconds, we are prepared to wait for a
* load request to complete.
* @type {number}
*/
goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
/**
* Optional parameters for goog.net.jsloader.send.
* timeout: The length of time, in milliseconds, we are prepared to wait
* for a load request to complete, or 0 or negative for no timeout. Default
* is 5 seconds.
* document: The HTML document under which to load the JavaScript. Default is
* the current document.
* cleanupWhenDone: If true clean up the script tag after script completes to
* load. This is important if you just want to read data from the JavaScript
* and then throw it away. Default is false.
* attributes: Additional attributes to set on the script tag.
*
* @typedef {{
* timeout: (number|undefined),
* document: (HTMLDocument|undefined),
* cleanupWhenDone: (boolean|undefined),
* attributes: (!Object<string, string>|undefined)
* }}
*/
goog.net.jsloader.Options;
/**
* Scripts (URIs) waiting to be loaded.
* @private {!Array<!goog.html.TrustedResourceUrl>}
*/
goog.net.jsloader.scriptsToLoad_ = [];
/**
* The deferred result of loading the URIs in scriptsToLoad_.
* We need to return this to a caller that wants to load URIs while
* a deferred is already working on them.
* @private {!goog.async.Deferred<null>}
*/
goog.net.jsloader.scriptLoadingDeferred_;
/**
* Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
* the order of script loads.
*
* Because we have to load the scripts in serial (load script 1, exec script 1,
* load script 2, exec script 2, and so on), this will be slower than doing
* the network fetches in parallel.
*
* If you need to load a large number of scripts but dependency order doesn't
* matter, you should just call goog.net.jsloader.safeLoad N times.
*
* If you need to load a large number of scripts on the same domain,
* you may want to use goog.module.ModuleLoader.
*
* @param {Array<!goog.html.TrustedResourceUrl>} trustedUris The URIs to load.
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
* goog.net.jsloader.options documentation for details.
* @return {!goog.async.Deferred} The deferred result, that may be used to add
* callbacks
*/
goog.net.jsloader.safeLoadMany = function(trustedUris, opt_options) {
// Loading the scripts in serial introduces asynchronosity into the flow.
// Therefore, there are race conditions where client A can kick off the load
// sequence for client B, even though client A's scripts haven't all been
// loaded yet.
//
// To work around this issue, all module loads share a queue.
if (!trustedUris.length) {
return goog.async.Deferred.succeed(null);
}
var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
goog.array.extend(goog.net.jsloader.scriptsToLoad_, trustedUris);
if (isAnotherModuleLoading) {
// jsloader is still loading some other scripts.
// In order to prevent the race condition noted above, we just add
// these URIs to the end of the scripts' queue and return the deferred
// result of the ongoing script load, so the caller knows when they
// finish loading.
return goog.net.jsloader.scriptLoadingDeferred_;
}
trustedUris = goog.net.jsloader.scriptsToLoad_;
var popAndLoadNextScript = function() {
var trustedUri = trustedUris.shift();
var deferred = goog.net.jsloader.safeLoad(trustedUri, opt_options);
if (trustedUris.length) {
deferred.addBoth(popAndLoadNextScript);
}
return deferred;
};
goog.net.jsloader.scriptLoadingDeferred_ = popAndLoadNextScript();
return goog.net.jsloader.scriptLoadingDeferred_;
};
/**
* Loads and evaluates a JavaScript file.
* When the script loads, a user callback is called.
* It is the client's responsibility to verify that the script ran successfully.
*
* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
* goog.net.jsloader.Options documentation for details.
* @return {!goog.async.Deferred} The deferred result, that may be used to add
* callbacks and/or cancel the transmission.
* The error callback will be called with a single goog.net.jsloader.Error
* parameter.
*/
goog.net.jsloader.safeLoad = function(trustedUri, opt_options) {
var options = opt_options || {};
var doc = options.document || document;
var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);
var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
var request = {script_: script, timeout_: undefined};
var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
// Set a timeout.
var timeout = null;
var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
options.timeout :
goog.net.jsloader.DEFAULT_TIMEOUT;
if (timeoutDuration > 0) {
timeout = window.setTimeout(function() {
goog.net.jsloader.cleanup_(script, true);
deferred.errback(
new goog.net.jsloader.Error(
goog.net.jsloader.ErrorCode.TIMEOUT,
'Timeout reached for loading script ' + uri));
}, timeoutDuration);
request.timeout_ = timeout;
}
// Hang the user callback to be called when the script completes to load.
// NOTE(user): This callback will be called in IE even upon error. In any
// case it is the client's responsibility to verify that the script ran
// successfully.
script.onload = script.onreadystatechange = function() {
if (!script.readyState || script.readyState == 'loaded' ||
script.readyState == 'complete') {
var removeScriptNode = options.cleanupWhenDone || false;
goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
deferred.callback(null);
}
};
// Add an error callback.
// NOTE(user): Not supported in IE.
script.onerror = function() {
goog.net.jsloader.cleanup_(script, true, timeout);
deferred.errback(
new goog.net.jsloader.Error(
goog.net.jsloader.ErrorCode.LOAD_ERROR,
'Error while loading script ' + uri));
};
var properties = options.attributes || {};
goog.object.extend(
properties, {'type': 'text/javascript', 'charset': 'UTF-8'});
goog.dom.setProperties(script, properties);
// NOTE(user): Safari never loads the script if we don't set the src
// attribute before appending.
goog.dom.safe.setScriptSrc(script, trustedUri);
var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
scriptParent.appendChild(script);
return deferred;
};
/**
* Loads a JavaScript file and verifies it was evaluated successfully, using a
* verification object.
* The verification object is set by the loaded JavaScript at the end of the
* script.
* We verify this object was set and return its value in the success callback.
* If the object is not defined we trigger an error callback.
*
* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.
* @param {string} verificationObjName The name of the verification object that
* the loaded script should set.
* @param {goog.net.jsloader.Options} options Optional parameters. See
* goog.net.jsloader.Options documentation for details.
* @return {!goog.async.Deferred} The deferred result, that may be used to add
* callbacks and/or cancel the transmission.
* The success callback will be called with a single parameter containing
* the value of the verification object.
* The error callback will be called with a single goog.net.jsloader.Error
* parameter.
*/
goog.net.jsloader.safeLoadAndVerify = function(
trustedUri, verificationObjName, options) {
// Define the global objects variable.
if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
}
var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);
// Verify that the expected object does not exist yet.
if (goog.isDef(verifyObjs[verificationObjName])) {
// TODO(user): Error or reset variable?
return goog.async.Deferred.fail(
new goog.net.jsloader.Error(
goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
'Verification object ' + verificationObjName +
' already defined.'));
}
// Send request to load the JavaScript.
var sendDeferred = goog.net.jsloader.safeLoad(trustedUri, options);
// Create a deferred object wrapping the send result.
var deferred =
new goog.async.Deferred(goog.bind(sendDeferred.cancel, sendDeferred));
// Call user back with object that was set by the script.
sendDeferred.addCallback(function() {
var result = verifyObjs[verificationObjName];
if (goog.isDef(result)) {
deferred.callback(result);
delete verifyObjs[verificationObjName];
} else {
// Error: script was not loaded properly.
deferred.errback(
new goog.net.jsloader.Error(
goog.net.jsloader.ErrorCode.VERIFY_ERROR, 'Script ' + uri +
' loaded, but verification object ' + verificationObjName +
' was not defined.'));
}
});
// Pass error to new deferred object.
sendDeferred.addErrback(function(error) {
if (goog.isDef(verifyObjs[verificationObjName])) {
delete verifyObjs[verificationObjName];
}
deferred.errback(error);
});
return deferred;
};
/**
* Gets the DOM element under which we should add new script elements.
* How? Take the first head element, and if not found take doc.documentElement,
* which always exists.
*
* @param {!HTMLDocument} doc The relevant document.
* @return {!Element} The script parent element.
* @private
*/
goog.net.jsloader.getScriptParentElement_ = function(doc) {
var headElements = goog.dom.getElementsByTagName(goog.dom.TagName.HEAD, doc);
if (!headElements || goog.array.isEmpty(headElements)) {
return doc.documentElement;
} else {
return headElements[0];
}
};
/**
* Cancels a given request.
* @this {{script_: Element, timeout_: number}} The request context.
* @private
*/
goog.net.jsloader.cancel_ = function() {
var request = this;
if (request && request.script_) {
var scriptNode = request.script_;
if (scriptNode && scriptNode.tagName == goog.dom.TagName.SCRIPT) {
goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
}
}
};
/**
* Removes the script node and the timeout.
*
* @param {Node} scriptNode The node to be cleaned up.
* @param {boolean} removeScriptNode If true completely remove the script node.
* @param {?number=} opt_timeout The timeout handler to cleanup.
* @private
*/
goog.net.jsloader.cleanup_ = function(
scriptNode, removeScriptNode, opt_timeout) {
if (goog.isDefAndNotNull(opt_timeout)) {
goog.global.clearTimeout(opt_timeout);
}
scriptNode.onload = goog.nullFunction;
scriptNode.onerror = goog.nullFunction;
scriptNode.onreadystatechange = goog.nullFunction;
// Do this after a delay (removing the script node of a running script can
// confuse older IEs).
if (removeScriptNode) {
window.setTimeout(function() { goog.dom.removeNode(scriptNode); }, 0);
}
};
/**
* Possible error codes for jsloader.
* @enum {number}
*/
goog.net.jsloader.ErrorCode = {
LOAD_ERROR: 0,
TIMEOUT: 1,
VERIFY_ERROR: 2,
VERIFY_OBJECT_ALREADY_EXISTS: 3
};
/**
* A jsloader error.
*
* @param {goog.net.jsloader.ErrorCode} code The error code.
* @param {string=} opt_message Additional message.
* @constructor
* @extends {goog.debug.Error}
* @final
*/
goog.net.jsloader.Error = function(code, opt_message) {
var msg = 'Jsloader error (code #' + code + ')';
if (opt_message) {
msg += ': ' + opt_message;
}
goog.net.jsloader.Error.base(this, 'constructor', msg);
/**
* The code for this error.
*
* @type {goog.net.jsloader.ErrorCode}
*/
this.code = code;
};
goog.inherits(goog.net.jsloader.Error, goog.debug.Error);

View file

@ -0,0 +1,381 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The original file lives here: http://go/cross_domain_channel.js
/**
* @fileoverview Implements a cross-domain communication channel. A
* typical web page is prevented by browser security from sending
* request, such as a XMLHttpRequest, to other servers than the ones
* from which it came. The Jsonp class provides a workaround by
* using dynamically generated script tags. Typical usage:.
*
* var jsonp = new goog.net.Jsonp(new goog.Uri('http://my.host.com/servlet'));
* var payload = { 'foo': 1, 'bar': true };
* jsonp.send(payload, function(reply) { alert(reply) });
*
* This script works in all browsers that are currently supported by
* the Google Maps API, which is IE 6.0+, Firefox 0.8+, Safari 1.2.4+,
* Netscape 7.1+, Mozilla 1.4+, Opera 8.02+.
*
*/
goog.provide('goog.net.Jsonp');
goog.require('goog.Uri');
goog.require('goog.html.legacyconversions');
goog.require('goog.net.jsloader');
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
/**
* Creates a new cross domain channel that sends data to the specified
* host URL. By default, if no reply arrives within 5s, the channel
* assumes the call failed to complete successfully.
*
* @param {goog.Uri|string} uri The Uri of the server side code that receives
* data posted through this channel (e.g.,
* "http://maps.google.com/maps/geo").
*
* @param {string=} opt_callbackParamName The parameter name that is used to
* specify the callback. Defaults to "callback".
*
* @constructor
* @final
*/
goog.net.Jsonp = function(uri, opt_callbackParamName) {
/**
* The uri_ object will be used to encode the payload that is sent to the
* server.
* @type {goog.Uri}
* @private
*/
this.uri_ = new goog.Uri(uri);
/**
* This is the callback parameter name that is added to the uri.
* @type {string}
* @private
*/
this.callbackParamName_ =
opt_callbackParamName ? opt_callbackParamName : 'callback';
/**
* The length of time, in milliseconds, this channel is prepared
* to wait for for a request to complete. The default value is 5 seconds.
* @type {number}
* @private
*/
this.timeout_ = 5000;
/**
* The nonce to use in the dynamically generated script tags. This is used for
* allowing the script callbacks to execute when the page has an enforced
* Content Security Policy.
* @type {string}
* @private
*/
this.nonce_ = '';
};
/**
* The prefix for the callback name which will be stored on goog.global.
*/
goog.net.Jsonp.CALLBACKS = '_callbacks_';
/**
* Used to generate unique callback IDs. The counter must be global because
* all channels share a common callback object.
* @private
*/
goog.net.Jsonp.scriptCounter_ = 0;
/**
* Static private method which returns the global unique callback id.
*
* @param {string} id The id of the script node.
* @return {string} A global unique id used to store callback on goog.global
* object.
* @private
*/
goog.net.Jsonp.getCallbackId_ = function(id) {
return goog.net.Jsonp.CALLBACKS + '__' + id;
};
/**
* Sets the length of time, in milliseconds, this channel is prepared
* to wait for for a request to complete. If the call is not competed
* within the set time span, it is assumed to have failed. To wait
* indefinitely for a request to complete set the timout to a negative
* number.
*
* @param {number} timeout The length of time before calls are
* interrupted.
*/
goog.net.Jsonp.prototype.setRequestTimeout = function(timeout) {
this.timeout_ = timeout;
};
/**
* Returns the current timeout value, in milliseconds.
*
* @return {number} The timeout value.
*/
goog.net.Jsonp.prototype.getRequestTimeout = function() {
return this.timeout_;
};
/**
* Sets the nonce value for CSP. This nonce value will be added to any created
* script elements and must match the nonce provided in the
* Content-Security-Policy header sent by the server for the callback to pass
* CSP enforcement.
*
* @param {string} nonce The CSP nonce value.
*/
goog.net.Jsonp.prototype.setNonce = function(nonce) {
this.nonce_ = nonce;
};
/**
* Sends the given payload to the URL specified at the construction
* time. The reply is delivered to the given replyCallback. If the
* errorCallback is specified and the reply does not arrive within the
* timeout period set on this channel, the errorCallback is invoked
* with the original payload.
*
* If no reply callback is specified, then the response is expected to
* consist of calls to globally registered functions. No &callback=
* URL parameter will be sent in the request, and the script element
* will be cleaned up after the timeout.
*
* @param {Object=} opt_payload Name-value pairs. If given, these will be
* added as parameters to the supplied URI as GET parameters to the
* given server URI.
*
* @param {Function=} opt_replyCallback A function expecting one
* argument, called when the reply arrives, with the response data.
*
* @param {Function=} opt_errorCallback A function expecting one
* argument, called on timeout, with the payload (if given), otherwise
* null.
*
* @param {string=} opt_callbackParamValue Value to be used as the
* parameter value for the callback parameter (callbackParamName).
* To be used when the value needs to be fixed by the client for a
* particular request, to make use of the cached responses for the request.
* NOTE: If multiple requests are made with the same
* opt_callbackParamValue, only the last call will work whenever the
* response comes back.
*
* @return {!Object} A request descriptor that may be used to cancel this
* transmission, or null, if the message may not be cancelled.
*/
goog.net.Jsonp.prototype.send = function(
opt_payload, opt_replyCallback, opt_errorCallback, opt_callbackParamValue) {
var payload = opt_payload || null;
var id = opt_callbackParamValue ||
'_' + (goog.net.Jsonp.scriptCounter_++).toString(36) +
goog.now().toString(36);
var callbackId = goog.net.Jsonp.getCallbackId_(id);
// Create a new Uri object onto which this payload will be added
var uri = this.uri_.clone();
if (payload) {
goog.net.Jsonp.addPayloadToUri_(payload, uri);
}
if (opt_replyCallback) {
var reply = goog.net.Jsonp.newReplyHandler_(id, opt_replyCallback);
// Register the callback on goog.global to make it discoverable
// by jsonp response.
goog.global[callbackId] = reply;
uri.setParameterValues(this.callbackParamName_, callbackId);
}
var options = {timeout: this.timeout_, cleanupWhenDone: true};
if (this.nonce_) {
options.attributes = {'nonce': this.nonce_};
}
var deferred = goog.net.jsloader.safeLoad(
goog.html.legacyconversions.trustedResourceUrlFromString(uri.toString()),
options);
var error = goog.net.Jsonp.newErrorHandler_(id, payload, opt_errorCallback);
deferred.addErrback(error);
return {id_: id, deferred_: deferred};
};
/**
* Cancels a given request. The request must be exactly the object returned by
* the send method.
*
* @param {Object} request The request object returned by the send method.
*/
goog.net.Jsonp.prototype.cancel = function(request) {
if (request) {
if (request.deferred_) {
request.deferred_.cancel();
}
if (request.id_) {
goog.net.Jsonp.cleanup_(request.id_, false);
}
}
};
/**
* Creates a timeout callback that calls the given timeoutCallback with the
* original payload.
*
* @param {string} id The id of the script node.
* @param {Object} payload The payload that was sent to the server.
* @param {Function=} opt_errorCallback The function called on timeout.
* @return {!Function} A zero argument function that handles callback duties.
* @private
*/
goog.net.Jsonp.newErrorHandler_ = function(id, payload, opt_errorCallback) {
/**
* When we call across domains with a request, this function is the
* timeout handler. Once it's done executing the user-specified
* error-handler, it removes the script node and original function.
*/
return function() {
goog.net.Jsonp.cleanup_(id, false);
if (opt_errorCallback) {
opt_errorCallback(payload);
}
};
};
/**
* Creates a reply callback that calls the given replyCallback with data
* returned by the server.
*
* @param {string} id The id of the script node.
* @param {Function} replyCallback The function called on reply.
* @return {!Function} A reply callback function.
* @private
*/
goog.net.Jsonp.newReplyHandler_ = function(id, replyCallback) {
/**
* This function is the handler for the all-is-well response. It
* clears the error timeout handler, calls the user's handler, then
* removes the script node and itself.
*
* @param {...Object} var_args The response data sent from the server.
*/
var handler = function(var_args) {
goog.net.Jsonp.cleanup_(id, true);
replyCallback.apply(undefined, arguments);
};
return handler;
};
/**
* Removes the reply handler registered on goog.global object.
*
* @param {string} id The id of the script node to be removed.
* @param {boolean} deleteReplyHandler If true, delete the reply handler
* instead of setting it to nullFunction (if we know the callback could
* never be called again).
* @private
*/
goog.net.Jsonp.cleanup_ = function(id, deleteReplyHandler) {
var callbackId = goog.net.Jsonp.getCallbackId_(id);
if (goog.global[callbackId]) {
if (deleteReplyHandler) {
try {
delete goog.global[callbackId];
} catch (e) {
// NOTE: Workaround to delete property on 'window' in IE <= 8, see:
// http://stackoverflow.com/questions/1073414/deleting-a-window-property-in-ie
goog.global[callbackId] = undefined;
}
} else {
// Removing the script tag doesn't necessarily prevent the script
// from firing, so we make the callback a noop.
goog.global[callbackId] = goog.nullFunction;
}
}
};
/**
* Returns URL encoded payload. The payload should be a map of name-value
* pairs, in the form {"foo": 1, "bar": true, ...}. If the map is empty,
* the URI will be unchanged.
*
* <p>The method uses hasOwnProperty() to assure the properties are on the
* object, not on its prototype.
*
* @param {!Object} payload A map of value name pairs to be encoded.
* A value may be specified as an array, in which case a query parameter
* will be created for each value, e.g.:
* {"foo": [1,2]} will encode to "foo=1&foo=2".
*
* @param {!goog.Uri} uri A Uri object onto which the payload key value pairs
* will be encoded.
*
* @return {!goog.Uri} A reference to the Uri sent as a parameter.
* @private
*/
goog.net.Jsonp.addPayloadToUri_ = function(payload, uri) {
for (var name in payload) {
// NOTE(user): Safari/1.3 doesn't have hasOwnProperty(). In that
// case, we iterate over all properties as a very lame workaround.
if (!payload.hasOwnProperty || payload.hasOwnProperty(name)) {
uri.setParameterValues(name, payload[name]);
}
}
return uri;
};
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
//
// This class allows us (Google) to send data from non-Google and thus
// UNTRUSTED pages to our servers. Under NO CIRCUMSTANCES return
// anything sensitive, such as session or cookie specific data. Return
// only data that you want parties external to Google to have. Also
// NEVER use this method to send data from web pages to untrusted
// servers, or redirects to unknown servers (www.google.com/cache,
// /q=xx&btnl, /url, www.googlepages.com, etc.)
//
// WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

View file

@ -0,0 +1,70 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implementation of XmlHttpFactory which allows construction from
* simple factory methods.
* @author dbk@google.com (David Barrett-Kahn)
*/
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) {
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() {
return this.xhrFactory_();
};
/** @override */
goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
return this.optionsFactory_();
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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 {string}
* @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
*/
goog.net.XhrLike.prototype.responseText;
/**
* @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() {};

View file

@ -0,0 +1,248 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Low level handling of XMLHttpRequest.
* @author arv@google.com (Erik Arvidsson)
* @author dbk@google.com (David Barrett-Kahn)
*/
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');
/**
* Static class for creating XMLHttpRequest objects.
* @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object.
*/
goog.net.XmlHttp = function() {
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
* {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead.
* TODO(ruilopes): Collapse both defines.
*/
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.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() {
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) {
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) {
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() {
goog.net.XmlHttpFactory.call(this);
};
goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
/** @override */
goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
var progId = this.getProgId_();
if (progId) {
return new ActiveXObject(progId);
} else {
return new XMLHttpRequest();
}
};
/** @override */
goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
var progId = this.getProgId_();
var 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() {
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.
var ACTIVE_X_IDENTS = [
'MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
var 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 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());

View file

@ -0,0 +1,66 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Interface for a factory for creating XMLHttpRequest objects
* and metadata about them.
* @author dbk@google.com (David Barrett-Kahn)
*/
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() {
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;

View file

@ -0,0 +1,751 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for manipulating objects/maps/hashes.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.object');
/**
* Whether two values are not observably distinguishable. This
* correctly detects that 0 is not the same as -0 and two NaNs are
* practically equivalent.
*
* The implementation is as suggested by harmony:egal proposal.
*
* @param {*} v The first value to compare.
* @param {*} v2 The second value to compare.
* @return {boolean} Whether two values are not observably distinguishable.
* @see http://wiki.ecmascript.org/doku.php?id=harmony:egal
*/
goog.object.is = function(v, v2) {
if (v === v2) {
// 0 === -0, but they are not identical.
// We need the cast because the compiler requires that v2 is a
// number (although 1/v2 works with non-number). We cast to ? to
// stop the compiler from type-checking this statement.
return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);
}
// NaN is non-reflexive: NaN !== NaN, although they are identical.
return v !== v && v2 !== v2;
};
/**
* 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.
* @template T,K,V
*/
goog.object.forEach = function(obj, f, opt_obj) {
for (var 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
*/
goog.object.filter = function(obj, f, opt_obj) {
var res = {};
for (var 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
*/
goog.object.map = function(obj, f, opt_obj) {
var res = {};
for (var 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
*/
goog.object.some = function(obj, f, opt_obj) {
for (var 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
*/
goog.object.every = function(obj, f, opt_obj) {
for (var 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.
*/
goog.object.getCount = function(obj) {
var rv = 0;
for (var 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.
*/
goog.object.getAnyKey = function(obj) {
for (var 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
*/
goog.object.getAnyValue = function(obj) {
for (var key in obj) {
return obj[key];
}
};
/**
* Whether the object/hash/map contains the given object as a value.
* An alias for goog.object.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
*/
goog.object.contains = function(obj, val) {
return goog.object.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
*/
goog.object.getValues = function(obj) {
var res = [];
var i = 0;
for (var 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.
*/
goog.object.getKeys = function(obj) {
var res = [];
var i = 0;
for (var 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
* is undefined, returns undefined.
*/
goog.object.getValueByKeys = function(obj, var_args) {
var isArrayLike = goog.isArrayLike(var_args);
var keys = isArrayLike ? var_args : arguments;
// Start with the 2nd parameter for the variable parameters syntax.
for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
obj = obj[keys[i]];
if (!goog.isDef(obj)) {
break;
}
}
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.
*/
goog.object.containsKey = function(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
*/
goog.object.containsValue = function(obj, val) {
for (var 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=} opt_this 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
*/
goog.object.findKey = function(obj, f, opt_this) {
for (var key in obj) {
if (f.call(/** @type {?} */ (opt_this), 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=} opt_this 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
*/
goog.object.findValue = function(obj, f, opt_this) {
var key = goog.object.findKey(obj, f, opt_this);
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.
*/
goog.object.isEmpty = function(obj) {
for (var key in obj) {
return false;
}
return true;
};
/**
* Removes all key value pairs from the object/map/hash.
*
* @param {Object} obj The object to clear.
*/
goog.object.clear = function(obj) {
for (var 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.
*/
goog.object.remove = function(obj, key) {
var 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.
* @template K,V
*/
goog.object.add = function(obj, key, val) {
if (obj !== null && key in obj) {
throw Error('The object already contains the key "' + key + '"');
}
goog.object.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=} opt_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
*/
goog.object.get = function(obj, key, opt_val) {
if (obj !== null && key in obj) {
return obj[key];
}
return opt_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
*/
goog.object.set = function(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
*/
goog.object.setIfUndefined = function(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 for use with a map used a 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
*/
goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
if (key in obj) {
return obj[key];
}
var 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
*/
goog.object.equals = function(a, b) {
for (var k in a) {
if (!(k in b) || a[k] !== b[k]) {
return false;
}
}
for (var 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
*/
goog.object.clone = function(obj) {
// We cannot use the prototype trick because a lot of methods depend on where
// the actual key is set.
var res = {};
for (var key in obj) {
res[key] = obj[key];
}
return res;
// We could also use goog.mixin but I wanted this to be independent from that.
};
/**
* Clones a value. The input may be an Object, Array, or basic type. Objects and
* arrays will be cloned recursively.
*
* WARNINGS:
* <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
* that refer to themselves will cause infinite recursion.
*
* <code>goog.object.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
*/
goog.object.unsafeClone = function(obj) {
var type = goog.typeOf(obj);
if (type == 'object' || type == 'array') {
if (goog.isFunction(obj.clone)) {
return obj.clone();
}
var clone = type == 'array' ? [] : {};
for (var key in obj) {
clone[key] = goog.object.unsafeClone(obj[key]);
}
return clone;
}
return obj;
};
/**
* 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.
*/
goog.object.transpose = function(obj) {
var transposed = {};
for (var key in obj) {
transposed[obj[key]] = key;
}
return transposed;
};
/**
* The names of the fields that are defined on Object.prototype.
* @type {Array<string>}
* @private
*/
goog.object.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 = {};
* goog.object.extend(o, {a: 0, b: 1});
* o; // {a: 0, b: 1}
* goog.object.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
* {@code var_args}.
* @param {...Object} var_args The objects from which values will be copied.
*/
goog.object.extend = function(target, var_args) {
var key, source;
for (var 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 (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
key = goog.object.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.
*/
goog.object.create = function(var_args) {
var argLength = arguments.length;
if (argLength == 1 && goog.isArray(arguments[0])) {
return goog.object.create.apply(null, arguments[0]);
}
if (argLength % 2) {
throw Error('Uneven number of arguments');
}
var rv = {};
for (var 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.
*/
goog.object.createSet = function(var_args) {
var argLength = arguments.length;
if (argLength == 1 && goog.isArray(arguments[0])) {
return goog.object.createSet.apply(null, arguments[0]);
}
var rv = {};
for (var 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
*/
goog.object.createImmutableView = function(obj) {
var 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.
*/
goog.object.isImmutableView = function(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 {@code Object.getOwnPropertyNames} nor
* {@code Object.getPrototypeOf} then this is equivalent to using {@code
* goog.object.getKeys}
*
* @param {?Object} obj The object to get the properties of.
* @param {boolean=} opt_includeObjectPrototype Whether properties defined on
* {@code Object.prototype} should be included in the result.
* @param {boolean=} opt_includeFunctionPrototype Whether properties defined on
* {@code Function.prototype} should be included in the result.
* @return {!Array<string>}
* @public
*/
goog.object.getAllPropertyNames = function(
obj, opt_includeObjectPrototype, opt_includeFunctionPrototype) {
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 goog.object.getKeys(obj);
}
var visitedSet = {};
// Traverse the prototype chain and add all properties to the visited set.
var proto = obj;
while (proto &&
(proto !== Object.prototype || !!opt_includeObjectPrototype) &&
(proto !== Function.prototype || !!opt_includeFunctionPrototype)) {
var names = Object.getOwnPropertyNames(proto);
for (var i = 0; i < names.length; i++) {
visitedSet[names[i]] = true;
}
proto = Object.getPrototypeOf(proto);
}
return goog.object.getKeys(visitedSet);
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.promise.Resolver');
/**
* 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;

View file

@ -0,0 +1,132 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.Thenable');
/** @suppress {extraRequire} */
goog.forwardDeclare('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 {@code 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 {@code onRejected} callback will be invoked
* with the rejection reason as argument, and the child Promise will be rejected
* with the return value of the callback or thrown value.
*
* @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
* {@code 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 bracket notation instead of goog.exportSymbol() so that the compiler
// won't create a 'var ctor;' extern when the "create externs from exports"
// mode is enabled.
ctor.prototype['then'] = ctor.prototype.then;
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 {@code goog.Thenable}.
* The class/superclass of the instance must call {@code addImplementation}.
*/
goog.Thenable.isImplementedBy = function(object) {
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;
}
};

View file

@ -0,0 +1,138 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Useful compiler idioms.
*
* @author johnlenz@google.com (John Lenz)
*/
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) {
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) {
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) {
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) {
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 {@code opt_keyFn} or {@code 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 {@code valueFn} and {@code opt_keyFn}
* allows unused calls to {@code 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 {@code opt_keyFn} should be provided. The key is also used as the
* parameter to the {@code 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) {
var storedKey = opt_keyFn ? opt_keyFn(key) : key;
if (Object.prototype.hasOwnProperty.call(cacheObj, storedKey)) {
return cacheObj[storedKey];
}
return (cacheObj[storedKey] = valueFn(key));
};

View file

@ -0,0 +1,186 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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 {@code 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
* {@code 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.
*
* @see goog.string.Const#from
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.string.Const = function() {
/**
* 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_ = '';
/**
* 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 a string.
*
* IMPORTANT: In code where it is security-relevant that an object's type is
* indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap}
* instead of this method.
*
* @see goog.string.Const#unwrap
* @override
*/
goog.string.Const.prototype.getTypedStringValue = function() {
return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
};
/**
* Returns a debug-string representation of this value.
*
* To obtain the actual string value wrapped inside an object of this type,
* use {@code goog.string.Const.unwrap}.
*
* @see goog.string.Const#unwrap
* @override
*/
goog.string.Const.prototype.toString = function() {
return 'Const{' +
this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
'}';
};
/**
* Performs a runtime check that the provided object is indeed an instance
* of {@code 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, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.string.Const.unwrap = function(stringConst) {
// 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-contant 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) {
return goog.string.Const.create__googStringSecurityPrivate_(s);
};
/**
* Type marker for the Const type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.string.Const.TYPE_MARKER_ = {};
/**
* Utility method to create Const instances.
* @param {string} s The string to initialize the Const object with.
* @return {!goog.string.Const} The initialized Const object.
* @private
*/
goog.string.Const.create__googStringSecurityPrivate_ = function(s) {
var stringConst = new goog.string.Const();
stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ =
s;
return stringConst;
};
/**
* A Const instance wrapping the empty string.
* @const {!goog.string.Const}
*/
goog.string.Const.EMPTY = goog.string.Const.from('');

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview 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) {
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) {
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 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 (var i = 1; i < arguments.length; i++) {
this.buffer_ += arguments[i];
}
}
return this;
};
/**
* Clears the internal buffer.
*/
goog.string.StringBuffer.prototype.clear = function() {
this.buffer_ = '';
};
/**
* @return {number} the length of the current contents of the buffer.
*/
goog.string.StringBuffer.prototype.getLength = function() {
return this.buffer_.length;
};
/**
* @return {string} The concatenated string.
* @override
*/
goog.string.StringBuffer.prototype.toString = function() {
return this.buffer_;
};

View file

@ -0,0 +1,48 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.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 {@code 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 {@code 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 {@code 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;

View file

@ -0,0 +1,458 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Datastructure: Hash Map.
*
* @author arv@google.com (Erik Arvidsson)
*
* 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.
*/
goog.provide('goog.structs.Map');
goog.require('goog.iter.Iterator');
goog.require('goog.iter.StopIteration');
goog.require('goog.object');
/**
* 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) {
/**
* 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.
* @private {number}
*/
this.count_ = 0;
/**
* Version used to detect changes while iterating.
* @private {number}
*/
this.version_ = 0;
var argLength = arguments.length;
if (argLength > 1) {
if (argLength % 2) {
throw 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.
*/
goog.structs.Map.prototype.getCount = function() {
return this.count_;
};
/**
* Returns the values of the map.
* @return {!Array<V>} The values in the map.
*/
goog.structs.Map.prototype.getValues = function() {
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.
*/
goog.structs.Map.prototype.getKeys = function() {
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.
*/
goog.structs.Map.prototype.containsKey = function(key) {
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) {
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.
*/
goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
if (this === otherMap) {
return true;
}
if (this.count_ != 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) {
return a === b;
};
/**
* @return {boolean} Whether the map is empty.
*/
goog.structs.Map.prototype.isEmpty = function() {
return this.count_ == 0;
};
/**
* Removes all key-value pairs from the map.
*/
goog.structs.Map.prototype.clear = function() {
this.map_ = {};
this.keys_.length = 0;
this.count_ = 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.
*/
goog.structs.Map.prototype.remove = function(key) {
if (goog.structs.Map.hasKey_(this.map_, key)) {
delete this.map_[key];
this.count_--;
this.version_++;
// clean up the keys array if the threshold is hit
if (this.keys_.length > 2 * this.count_) {
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() {
if (this.count_ != 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.count_ != 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 {@code 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) {
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.
* @return {*} Some subclasses return a value.
*/
goog.structs.Map.prototype.set = function(key, value) {
if (!(goog.structs.Map.hasKey_(this.map_, key))) {
this.count_++;
// 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.
*/
goog.structs.Map.prototype.addAll = function(map) {
var keys, values;
if (map instanceof goog.structs.Map) {
keys = map.getKeys();
values = map.getValues();
} else {
keys = goog.object.getKeys(map);
values = goog.object.getValues(map);
}
// we could use goog.array.forEach here but I don't want to introduce that
// dependency just for this.
for (var i = 0; i < keys.length; i++) {
this.set(keys[i], values[i]);
}
};
/**
* 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
*/
goog.structs.Map.prototype.forEach = function(f, opt_obj) {
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.
*/
goog.structs.Map.prototype.clone = function() {
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.
*/
goog.structs.Map.prototype.transpose = function() {
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.
*/
goog.structs.Map.prototype.toObject = function() {
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.
*/
goog.structs.Map.prototype.getKeyIterator = function() {
return this.__iterator__(true);
};
/**
* 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.
*/
goog.structs.Map.prototype.getValueIterator = function() {
return this.__iterator__(false);
};
/**
* 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.
*/
goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
// 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.next = function() {
if (version != selfObj.version_) {
throw 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;
};
/**
* 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) {
return Object.prototype.hasOwnProperty.call(obj, key);
};

View file

@ -0,0 +1,354 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Generics method for collection-like classes and objects.
*
* @author arv@google.com (Erik Arvidsson)
*
* This file contains functions to work with collections. It supports using
* Map, Set, Array and Object and other classes that implement collection-like
* methods.
*/
goog.provide('goog.structs');
goog.require('goog.array');
goog.require('goog.object');
// We treat an object as a dictionary if it has getKeys or it is an object that
// isn't arrayLike.
/**
* Returns the number of values in the collection-like object.
* @param {Object} col The collection-like object.
* @return {number} The number of values in the collection-like object.
*/
goog.structs.getCount = function(col) {
if (col.getCount && typeof col.getCount == 'function') {
return col.getCount();
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return col.length;
}
return goog.object.getCount(col);
};
/**
* Returns the values of the collection-like object.
* @param {Object} col The collection-like object.
* @return {!Array<?>} The values in the collection-like object.
*/
goog.structs.getValues = function(col) {
if (col.getValues && typeof col.getValues == 'function') {
return col.getValues();
}
if (goog.isString(col)) {
return col.split('');
}
if (goog.isArrayLike(col)) {
var rv = [];
var l = col.length;
for (var i = 0; i < l; i++) {
rv.push(col[i]);
}
return rv;
}
return goog.object.getValues(col);
};
/**
* Returns the keys of the collection. Some collections have no notion of
* keys/indexes and this function will return undefined in those cases.
* @param {Object} col The collection-like object.
* @return {!Array|undefined} The keys in the collection.
*/
goog.structs.getKeys = function(col) {
if (col.getKeys && typeof col.getKeys == 'function') {
return col.getKeys();
}
// if we have getValues but no getKeys we know this is a key-less collection
if (col.getValues && typeof col.getValues == 'function') {
return undefined;
}
if (goog.isArrayLike(col) || goog.isString(col)) {
var rv = [];
var l = col.length;
for (var i = 0; i < l; i++) {
rv.push(i);
}
return rv;
}
return goog.object.getKeys(col);
};
/**
* Whether the collection contains the given value. This is O(n) and uses
* equals (==) to test the existence.
* @param {Object} col The collection-like object.
* @param {*} val The value to check for.
* @return {boolean} True if the map contains the value.
*/
goog.structs.contains = function(col, val) {
if (col.contains && typeof col.contains == 'function') {
return col.contains(val);
}
if (col.containsValue && typeof col.containsValue == 'function') {
return col.containsValue(val);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.contains(/** @type {!Array<?>} */ (col), val);
}
return goog.object.containsValue(col, val);
};
/**
* Whether the collection is empty.
* @param {Object} col The collection-like object.
* @return {boolean} True if empty.
*/
goog.structs.isEmpty = function(col) {
if (col.isEmpty && typeof col.isEmpty == 'function') {
return col.isEmpty();
}
// We do not use goog.string.isEmptyOrWhitespace because here we treat the
// string as
// collection and as such even whitespace matters
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.isEmpty(/** @type {!Array<?>} */ (col));
}
return goog.object.isEmpty(col);
};
/**
* Removes all the elements from the collection.
* @param {Object} col The collection-like object.
*/
goog.structs.clear = function(col) {
// NOTE(arv): This should not contain strings because strings are immutable
if (col.clear && typeof col.clear == 'function') {
col.clear();
} else if (goog.isArrayLike(col)) {
goog.array.clear(/** @type {IArrayLike<?>} */ (col));
} else {
goog.object.clear(col);
}
};
/**
* Calls a function for each value in a collection. The function takes
* three arguments; the value, the key and the collection.
*
* @param {S} col The collection-like object.
* @param {function(this:T,?,?,S):?} f The function to call for every value.
* This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and the return value is irrelevant.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @template T,S
* @deprecated Use a more specific method, e.g. goog.array.forEach,
* goog.object.forEach, or for-of.
*/
goog.structs.forEach = function(col, f, opt_obj) {
if (col.forEach && typeof col.forEach == 'function') {
col.forEach(f, opt_obj);
} else if (goog.isArrayLike(col) || goog.isString(col)) {
goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj);
} else {
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col);
}
}
};
/**
* Calls a function for every value in the collection. When a call returns true,
* adds the value to a new collection (Array is returned by default).
*
* @param {S} col The collection-like object.
* @param {function(this:T,?,?,S):boolean} f The function to call for every
* value. This function takes
* 3 arguments (the value, the key or undefined if the collection has no
* notion of keys, and the collection) and should return a Boolean. If the
* return value is true the value is added to the result collection. If it
* is false the value is not included.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {!Object|!Array<?>} A new collection where the passed values are
* present. If col is a key-less collection an array is returned. If col
* has keys and values a plain old JS object is returned.
* @template T,S
*/
goog.structs.filter = function(col, f, opt_obj) {
if (typeof col.filter == 'function') {
return col.filter(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj);
}
var rv;
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
if (keys) {
rv = {};
for (var i = 0; i < l; i++) {
if (f.call(/** @type {?} */ (opt_obj), values[i], keys[i], col)) {
rv[keys[i]] = values[i];
}
}
} else {
// We should not use goog.array.filter here since we want to make sure that
// the index is undefined as well as make sure that col is passed to the
// function.
rv = [];
for (var i = 0; i < l; i++) {
if (f.call(opt_obj, values[i], undefined, col)) {
rv.push(values[i]);
}
}
}
return rv;
};
/**
* Calls a function for every value in the collection and adds the result into a
* new collection (defaults to creating a new Array).
*
* @param {S} col The collection-like object.
* @param {function(this:T,?,?,S):V} f The function to call for every value.
* This function takes 3 arguments (the value, the key or undefined if the
* collection has no notion of keys, and the collection) and should return
* something. The result will be used as the value in the new collection.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {!Object<V>|!Array<V>} A new collection with the new values. If
* col is a key-less collection an array is returned. If col has keys and
* values a plain old JS object is returned.
* @template T,S,V
*/
goog.structs.map = function(col, f, opt_obj) {
if (typeof col.map == 'function') {
return col.map(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj);
}
var rv;
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
if (keys) {
rv = {};
for (var i = 0; i < l; i++) {
rv[keys[i]] = f.call(/** @type {?} */ (opt_obj), values[i], keys[i], col);
}
} else {
// We should not use goog.array.map here since we want to make sure that
// the index is undefined as well as make sure that col is passed to the
// function.
rv = [];
for (var i = 0; i < l; i++) {
rv[i] = f.call(/** @type {?} */ (opt_obj), values[i], undefined, col);
}
}
return rv;
};
/**
* Calls f for each value in a collection. If any call returns true this returns
* true (without checking the rest). If all returns false this returns false.
*
* @param {S} col The collection-like object.
* @param {function(this:T,?,?,S):boolean} f The function to call for every
* value. This function takes 3 arguments (the value, the key or undefined
* if the collection has no notion of keys, and the collection) and should
* return a boolean.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {boolean} True if any value passes the test.
* @template T,S
*/
goog.structs.some = function(col, f, opt_obj) {
if (typeof col.some == 'function') {
return col.some(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj);
}
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
if (f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col)) {
return true;
}
}
return false;
};
/**
* Calls f for each value in a collection. If all calls return true this return
* true this returns true. If any returns false this returns false at this point
* and does not continue to check the remaining values.
*
* @param {S} col The collection-like object.
* @param {function(this:T,?,?,S):boolean} f The function to call for every
* value. This function takes 3 arguments (the value, the key or
* undefined if the collection has no notion of keys, and the collection)
* and should return a boolean.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code f}.
* @return {boolean} True if all key-value pairs pass the test.
* @template T,S
*/
goog.structs.every = function(col, f, opt_obj) {
if (typeof col.every == 'function') {
return col.every(f, opt_obj);
}
if (goog.isArrayLike(col) || goog.isString(col)) {
return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj);
}
var keys = goog.structs.getKeys(col);
var values = goog.structs.getValues(col);
var l = values.length;
for (var i = 0; i < l; i++) {
if (!f.call(/** @type {?} */ (opt_obj), values[i], keys && keys[i], col)) {
return false;
}
}
return true;
};

View file

@ -0,0 +1,324 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A timer class to which other classes and objects can listen on.
* This is only an abstraction above {@code setInterval}.
*
* @see ../demos/timers.html
*/
goog.provide('goog.Timer');
goog.require('goog.Promise');
goog.require('goog.events.EventTarget');
/**
* Class for handling timing events.
*
* @param {number=} opt_interval Number of ms between ticks (default: 1ms).
* @param {Object=} opt_timerObject An object that has {@code setTimeout},
* {@code setInterval}, {@code clearTimeout} and {@code clearInterval}
* (e.g., {@code window}).
* @constructor
* @extends {goog.events.EventTarget}
*/
goog.Timer = function(opt_interval, opt_timerObject) {
goog.events.EventTarget.call(this);
/**
* Number of ms between ticks
* @private {number}
*/
this.interval_ = opt_interval || 1;
/**
* An object that implements {@code setTimeout}, {@code setInterval},
* {@code clearTimeout} and {@code clearInterval}. We default to the window
* object. Changing this on {@link goog.Timer.prototype} changes the object
* for all timer instances which can be useful if your environment has some
* other implementation of timers than the {@code window} object.
* @private {{setTimeout:!Function, clearTimeout:!Function}}
*/
this.timerObject_ = /** @type {{setTimeout, clearTimeout}} */ (
opt_timerObject || goog.Timer.defaultTimerObject);
/**
* Cached {@code tick_} bound to the object for later use in the timer.
* @private {Function}
* @const
*/
this.boundTick_ = goog.bind(this.tick_, this);
/**
* Firefox browser often fires the timer event sooner (sometimes MUCH sooner)
* than the requested timeout. So we compare the time to when the event was
* last fired, and reschedule if appropriate. See also
* {@link goog.Timer.intervalScale}.
* @private {number}
*/
this.last_ = goog.now();
};
goog.inherits(goog.Timer, goog.events.EventTarget);
/**
* Maximum timeout value.
*
* Timeout values too big to fit into a signed 32-bit integer may cause overflow
* in FF, Safari, and Chrome, resulting in the timeout being scheduled
* immediately. It makes more sense simply not to schedule these timeouts, since
* 24.8 days is beyond a reasonable expectation for the browser to stay open.
*
* @private {number}
* @const
*/
goog.Timer.MAX_TIMEOUT_ = 2147483647;
/**
* A timer ID that cannot be returned by any known implementation of
* {@code window.setTimeout}. Passing this value to {@code window.clearTimeout}
* should therefore be a no-op.
*
* @private {number}
* @const
*/
goog.Timer.INVALID_TIMEOUT_ID_ = -1;
/**
* Whether this timer is enabled
* @type {boolean}
*/
goog.Timer.prototype.enabled = false;
/**
* An object that implements {@code setTimeout}, {@code setInterval},
* {@code clearTimeout} and {@code clearInterval}. We default to the global
* object. Changing {@code goog.Timer.defaultTimerObject} changes the object for
* all timer instances which can be useful if your environment has some other
* implementation of timers you'd like to use.
* @type {{setTimeout, clearTimeout}}
*/
goog.Timer.defaultTimerObject = goog.global;
/**
* Variable that controls the timer error correction. If the timer is called
* before the requested interval times {@code intervalScale}, which often
* happens on Mozilla, the timer is rescheduled.
* @see {@link #last_}
* @type {number}
*/
goog.Timer.intervalScale = 0.8;
/**
* Variable for storing the result of {@code setInterval}.
* @private {?number}
*/
goog.Timer.prototype.timer_ = null;
/**
* Gets the interval of the timer.
* @return {number} interval Number of ms between ticks.
*/
goog.Timer.prototype.getInterval = function() {
return this.interval_;
};
/**
* Sets the interval of the timer.
* @param {number} interval Number of ms between ticks.
*/
goog.Timer.prototype.setInterval = function(interval) {
this.interval_ = interval;
if (this.timer_ && this.enabled) {
// Stop and then start the timer to reset the interval.
this.stop();
this.start();
} else if (this.timer_) {
this.stop();
}
};
/**
* Callback for the {@code setTimeout} used by the timer.
* @private
*/
goog.Timer.prototype.tick_ = function() {
if (this.enabled) {
var elapsed = goog.now() - this.last_;
if (elapsed > 0 && elapsed < this.interval_ * goog.Timer.intervalScale) {
this.timer_ = this.timerObject_.setTimeout(
this.boundTick_, this.interval_ - elapsed);
return;
}
// Prevents setInterval from registering a duplicate timeout when called
// in the timer event handler.
if (this.timer_) {
this.timerObject_.clearTimeout(this.timer_);
this.timer_ = null;
}
this.dispatchTick();
// The timer could be stopped in the timer event handler.
if (this.enabled) {
this.timer_ =
this.timerObject_.setTimeout(this.boundTick_, this.interval_);
this.last_ = goog.now();
}
}
};
/**
* Dispatches the TICK event. This is its own method so subclasses can override.
*/
goog.Timer.prototype.dispatchTick = function() {
this.dispatchEvent(goog.Timer.TICK);
};
/**
* Starts the timer.
*/
goog.Timer.prototype.start = function() {
this.enabled = true;
// If there is no interval already registered, start it now
if (!this.timer_) {
// IMPORTANT!
// window.setInterval in FireFox has a bug - it fires based on
// absolute time, rather than on relative time. What this means
// is that if a computer is sleeping/hibernating for 24 hours
// and the timer interval was configured to fire every 1000ms,
// then after the PC wakes up the timer will fire, in rapid
// succession, 3600*24 times.
// This bug is described here and is already fixed, but it will
// take time to propagate, so for now I am switching this over
// to setTimeout logic.
// https://bugzilla.mozilla.org/show_bug.cgi?id=376643
//
this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_);
this.last_ = goog.now();
}
};
/**
* Stops the timer.
*/
goog.Timer.prototype.stop = function() {
this.enabled = false;
if (this.timer_) {
this.timerObject_.clearTimeout(this.timer_);
this.timer_ = null;
}
};
/** @override */
goog.Timer.prototype.disposeInternal = function() {
goog.Timer.superClass_.disposeInternal.call(this);
this.stop();
delete this.timerObject_;
};
/**
* Constant for the timer's event type.
* @const
*/
goog.Timer.TICK = 'tick';
/**
* Calls the given function once, after the optional pause.
* <p>
* The function is always called asynchronously, even if the delay is 0. This
* is a common trick to schedule a function to run after a batch of browser
* event processing.
*
* @param {function(this:SCOPE)|{handleEvent:function()}|null} listener Function
* or object that has a handleEvent method.
* @param {number=} opt_delay Milliseconds to wait; default is 0.
* @param {SCOPE=} opt_handler Object in whose scope to call the listener.
* @return {number} A handle to the timer ID.
* @template SCOPE
*/
goog.Timer.callOnce = function(listener, opt_delay, opt_handler) {
if (goog.isFunction(listener)) {
if (opt_handler) {
listener = goog.bind(listener, opt_handler);
}
} else if (listener && typeof listener.handleEvent == 'function') {
// using typeof to prevent strict js warning
listener = goog.bind(listener.handleEvent, listener);
} else {
throw Error('Invalid listener argument');
}
if (Number(opt_delay) > goog.Timer.MAX_TIMEOUT_) {
// Timeouts greater than MAX_INT return immediately due to integer
// overflow in many browsers. Since MAX_INT is 24.8 days, just don't
// schedule anything at all.
return goog.Timer.INVALID_TIMEOUT_ID_;
} else {
return goog.Timer.defaultTimerObject.setTimeout(listener, opt_delay || 0);
}
};
/**
* Clears a timeout initiated by {@link #callOnce}.
* @param {?number} timerId A timer ID.
*/
goog.Timer.clear = function(timerId) {
goog.Timer.defaultTimerObject.clearTimeout(timerId);
};
/**
* @param {number} delay Milliseconds to wait.
* @param {(RESULT|goog.Thenable<RESULT>|Thenable)=} opt_result The value
* with which the promise will be resolved.
* @return {!goog.Promise<RESULT>} A promise that will be resolved after
* the specified delay, unless it is canceled first.
* @template RESULT
*/
goog.Timer.promise = function(delay, opt_result) {
var timerKey = null;
return new goog
.Promise(function(resolve, reject) {
timerKey =
goog.Timer.callOnce(function() { resolve(opt_result); }, delay);
if (timerKey == goog.Timer.INVALID_TIMEOUT_ID_) {
reject(new Error('Failed to schedule timer.'));
}
})
.thenCatch(function(error) {
// Clear the timer. The most likely reason is "cancel" signal.
goog.Timer.clear(timerKey);
throw error;
});
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,181 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Detects the specific browser and not just the rendering engine.
*
*/
goog.provide('goog.userAgent.product');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.userAgent');
/**
* @define {boolean} Whether the code is running on the Firefox web browser.
*/
goog.define('goog.userAgent.product.ASSUME_FIREFOX', false);
/**
* @define {boolean} Whether we know at compile-time that the product is an
* iPhone.
*/
goog.define('goog.userAgent.product.ASSUME_IPHONE', false);
/**
* @define {boolean} Whether we know at compile-time that the product is an
* iPad.
*/
goog.define('goog.userAgent.product.ASSUME_IPAD', false);
/**
* @define {boolean} Whether we know at compile-time that the product is an
* AOSP browser or WebView inside a pre KitKat Android phone or tablet.
*/
goog.define('goog.userAgent.product.ASSUME_ANDROID', false);
/**
* @define {boolean} Whether the code is running on the Chrome web browser on
* any platform or AOSP browser or WebView in a KitKat+ Android phone or tablet.
*/
goog.define('goog.userAgent.product.ASSUME_CHROME', false);
/**
* @define {boolean} Whether the code is running on the Safari web browser.
*/
goog.define('goog.userAgent.product.ASSUME_SAFARI', false);
/**
* Whether we know the product type at compile-time.
* @type {boolean}
* @private
*/
goog.userAgent.product.PRODUCT_KNOWN_ = goog.userAgent.ASSUME_IE ||
goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_OPERA ||
goog.userAgent.product.ASSUME_FIREFOX ||
goog.userAgent.product.ASSUME_IPHONE ||
goog.userAgent.product.ASSUME_IPAD ||
goog.userAgent.product.ASSUME_ANDROID ||
goog.userAgent.product.ASSUME_CHROME ||
goog.userAgent.product.ASSUME_SAFARI;
/**
* Whether the code is running on the Opera web browser.
* @type {boolean}
*/
goog.userAgent.product.OPERA = goog.userAgent.OPERA;
/**
* Whether the code is running on an IE web browser.
* @type {boolean}
*/
goog.userAgent.product.IE = goog.userAgent.IE;
/**
* Whether the code is running on an Edge web browser.
* @type {boolean}
*/
goog.userAgent.product.EDGE = goog.userAgent.EDGE;
/**
* Whether the code is running on the Firefox web browser.
* @type {boolean}
*/
goog.userAgent.product.FIREFOX = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_FIREFOX :
goog.labs.userAgent.browser.isFirefox();
/**
* Whether the user agent is an iPhone or iPod (as in iPod touch).
* @return {boolean}
* @private
*/
goog.userAgent.product.isIphoneOrIpod_ = function() {
return goog.labs.userAgent.platform.isIphone() ||
goog.labs.userAgent.platform.isIpod();
};
/**
* Whether the code is running on an iPhone or iPod touch.
*
* iPod touch is considered an iPhone for legacy reasons.
* @type {boolean}
*/
goog.userAgent.product.IPHONE = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_IPHONE :
goog.userAgent.product.isIphoneOrIpod_();
/**
* Whether the code is running on an iPad.
* @type {boolean}
*/
goog.userAgent.product.IPAD = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_IPAD :
goog.labs.userAgent.platform.isIpad();
/**
* Whether the code is running on AOSP browser or WebView inside
* a pre KitKat Android phone or tablet.
* @type {boolean}
*/
goog.userAgent.product.ANDROID = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_ANDROID :
goog.labs.userAgent.browser.isAndroidBrowser();
/**
* Whether the code is running on the Chrome web browser on any platform
* or AOSP browser or WebView in a KitKat+ Android phone or tablet.
* @type {boolean}
*/
goog.userAgent.product.CHROME = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_CHROME :
goog.labs.userAgent.browser.isChrome();
/**
* @return {boolean} Whether the browser is Safari on desktop.
* @private
*/
goog.userAgent.product.isSafariDesktop_ = function() {
return goog.labs.userAgent.browser.isSafari() &&
!goog.labs.userAgent.platform.isIos();
};
/**
* Whether the code is running on the desktop Safari web browser.
* Note: the legacy behavior here is only true for Safari not running
* on iOS.
* @type {boolean}
*/
goog.userAgent.product.SAFARI = goog.userAgent.product.PRODUCT_KNOWN_ ?
goog.userAgent.product.ASSUME_SAFARI :
goog.userAgent.product.isSafariDesktop_();

View file

@ -0,0 +1,580 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Rendering engine detection.
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
* For information on the browser brand (such as Safari versus Chrome), see
* goog.userAgent.product.
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/useragent.html
*/
goog.provide('goog.userAgent');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.engine');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.util');
goog.require('goog.reflect');
goog.require('goog.string');
/**
* @define {boolean} Whether we know at compile-time that the browser is IE.
*/
goog.define('goog.userAgent.ASSUME_IE', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is EDGE.
*/
goog.define('goog.userAgent.ASSUME_EDGE', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is GECKO.
*/
goog.define('goog.userAgent.ASSUME_GECKO', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
*/
goog.define('goog.userAgent.ASSUME_WEBKIT', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is a
* mobile device running WebKit e.g. iPhone or Android.
*/
goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is OPERA.
*/
goog.define('goog.userAgent.ASSUME_OPERA', false);
/**
* @define {boolean} Whether the
* {@code goog.userAgent.isVersionOrHigher}
* function will return true for any version.
*/
goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
/**
* Whether we know the browser engine at compile-time.
* @type {boolean}
* @private
*/
goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE ||
goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_GECKO ||
goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT ||
goog.userAgent.ASSUME_OPERA;
/**
* Returns the userAgent string for the current browser.
*
* @return {string} The userAgent string.
*/
goog.userAgent.getUserAgentString = function() {
return goog.labs.userAgent.util.getUserAgent();
};
/**
* TODO(nnaze): Change type to "Navigator" and update compilation targets.
* @return {?Object} The native navigator object.
*/
goog.userAgent.getNavigator = function() {
// Need a local navigator reference instead of using the global one,
// to avoid the rare case where they reference different objects.
// (in a WorkerPool, for example).
return goog.global['navigator'] || null;
};
/**
* Whether the user agent is Opera.
* @type {boolean}
*/
goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_OPERA :
goog.labs.userAgent.browser.isOpera();
/**
* Whether the user agent is Internet Explorer.
* @type {boolean}
*/
goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_IE :
goog.labs.userAgent.browser.isIE();
/**
* Whether the user agent is Microsoft Edge.
* @type {boolean}
*/
goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_EDGE :
goog.labs.userAgent.engine.isEdge();
/**
* Whether the user agent is MS Internet Explorer or MS Edge.
* @type {boolean}
*/
goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
/**
* Whether the user agent is Gecko. Gecko is the rendering engine used by
* Mozilla, Firefox, and others.
* @type {boolean}
*/
goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_GECKO :
goog.labs.userAgent.engine.isGecko();
/**
* Whether the user agent is WebKit. WebKit is the rendering engine that
* Safari, Android and others use.
* @type {boolean}
*/
goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
goog.labs.userAgent.engine.isWebKit();
/**
* Whether the user agent is running on a mobile device.
*
* This is a separate function so that the logic can be tested.
*
* TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
*
* @return {boolean} Whether the user agent is running on a mobile device.
* @private
*/
goog.userAgent.isMobile_ = function() {
return goog.userAgent.WEBKIT &&
goog.labs.userAgent.util.matchUserAgent('Mobile');
};
/**
* Whether the user agent is running on a mobile device.
*
* TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
* is promoted as the gecko/webkit logic is likely inaccurate.
*
* @type {boolean}
*/
goog.userAgent.MOBILE =
goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.isMobile_();
/**
* Used while transitioning code to use WEBKIT instead.
* @type {boolean}
* @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
* TODO(nicksantos): Delete this from goog.userAgent.
*/
goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
/**
* @return {string} the platform (operating system) the user agent is running
* on. Default to empty string because navigator.platform may not be defined
* (on Rhino, for example).
* @private
*/
goog.userAgent.determinePlatform_ = function() {
var navigator = goog.userAgent.getNavigator();
return navigator && navigator.platform || '';
};
/**
* The platform (operating system) the user agent is running on. Default to
* empty string because navigator.platform may not be defined (on Rhino, for
* example).
* @type {string}
*/
goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
/**
* @define {boolean} Whether the user agent is running on a Macintosh operating
* system.
*/
goog.define('goog.userAgent.ASSUME_MAC', false);
/**
* @define {boolean} Whether the user agent is running on a Windows operating
* system.
*/
goog.define('goog.userAgent.ASSUME_WINDOWS', false);
/**
* @define {boolean} Whether the user agent is running on a Linux operating
* system.
*/
goog.define('goog.userAgent.ASSUME_LINUX', false);
/**
* @define {boolean} Whether the user agent is running on a X11 windowing
* system.
*/
goog.define('goog.userAgent.ASSUME_X11', false);
/**
* @define {boolean} Whether the user agent is running on Android.
*/
goog.define('goog.userAgent.ASSUME_ANDROID', false);
/**
* @define {boolean} Whether the user agent is running on an iPhone.
*/
goog.define('goog.userAgent.ASSUME_IPHONE', false);
/**
* @define {boolean} Whether the user agent is running on an iPad.
*/
goog.define('goog.userAgent.ASSUME_IPAD', false);
/**
* @define {boolean} Whether the user agent is running on an iPod.
*/
goog.define('goog.userAgent.ASSUME_IPOD', false);
/**
* @type {boolean}
* @private
*/
goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC ||
goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX ||
goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID ||
goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
goog.userAgent.ASSUME_IPOD;
/**
* Whether the user agent is running on a Macintosh operating system.
* @type {boolean}
*/
goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_MAC :
goog.labs.userAgent.platform.isMacintosh();
/**
* Whether the user agent is running on a Windows operating system.
* @type {boolean}
*/
goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_WINDOWS :
goog.labs.userAgent.platform.isWindows();
/**
* Whether the user agent is Linux per the legacy behavior of
* goog.userAgent.LINUX, which considered ChromeOS to also be
* Linux.
* @return {boolean}
* @private
*/
goog.userAgent.isLegacyLinux_ = function() {
return goog.labs.userAgent.platform.isLinux() ||
goog.labs.userAgent.platform.isChromeOS();
};
/**
* Whether the user agent is running on a Linux operating system.
*
* Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
* while goog.labs.userAgent.platform considers ChromeOS and
* Linux to be different OSes.
*
* @type {boolean}
*/
goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_LINUX :
goog.userAgent.isLegacyLinux_();
/**
* @return {boolean} Whether the user agent is an X11 windowing system.
* @private
*/
goog.userAgent.isX11_ = function() {
var navigator = goog.userAgent.getNavigator();
return !!navigator &&
goog.string.contains(navigator['appVersion'] || '', 'X11');
};
/**
* Whether the user agent is running on a X11 windowing system.
* @type {boolean}
*/
goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_X11 :
goog.userAgent.isX11_();
/**
* Whether the user agent is running on Android.
* @type {boolean}
*/
goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_ANDROID :
goog.labs.userAgent.platform.isAndroid();
/**
* Whether the user agent is running on an iPhone.
* @type {boolean}
*/
goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPHONE :
goog.labs.userAgent.platform.isIphone();
/**
* Whether the user agent is running on an iPad.
* @type {boolean}
*/
goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPAD :
goog.labs.userAgent.platform.isIpad();
/**
* Whether the user agent is running on an iPod.
* @type {boolean}
*/
goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPOD :
goog.labs.userAgent.platform.isIpod();
/**
* Whether the user agent is running on iOS.
* @type {boolean}
*/
goog.userAgent.IOS = goog.userAgent.PLATFORM_KNOWN_ ?
(goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
goog.userAgent.ASSUME_IPOD) :
goog.labs.userAgent.platform.isIos();
/**
* @return {string} The string that describes the version number of the user
* agent.
* @private
*/
goog.userAgent.determineVersion_ = function() {
// All browsers have different ways to detect the version and they all have
// different naming schemes.
// version is a string rather than a number because it may contain 'b', 'a',
// and so on.
var version = '';
var arr = goog.userAgent.getVersionRegexResult_();
if (arr) {
version = arr ? arr[1] : '';
}
if (goog.userAgent.IE) {
// IE9 can be in document mode 9 but be reporting an inconsistent user agent
// version. If it is identifying as a version lower than 9 we take the
// documentMode as the version instead. IE8 has similar behavior.
// It is recommended to set the X-UA-Compatible header to ensure that IE9
// uses documentMode 9.
var docMode = goog.userAgent.getDocumentMode_();
if (docMode != null && docMode > parseFloat(version)) {
return String(docMode);
}
}
return version;
};
/**
* @return {?Array|undefined} The version regex matches from parsing the user
* agent string. These regex statements must be executed inline so they can
* be compiled out by the closure compiler with the rest of the useragent
* detection logic when ASSUME_* is specified.
* @private
*/
goog.userAgent.getVersionRegexResult_ = function() {
var userAgent = goog.userAgent.getUserAgentString();
if (goog.userAgent.GECKO) {
return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
}
if (goog.userAgent.EDGE) {
return /Edge\/([\d\.]+)/.exec(userAgent);
}
if (goog.userAgent.IE) {
return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
}
if (goog.userAgent.WEBKIT) {
// WebKit/125.4
return /WebKit\/(\S+)/.exec(userAgent);
}
if (goog.userAgent.OPERA) {
// If none of the above browsers were detected but the browser is Opera, the
// only string that is of interest is 'Version/<number>'.
return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
}
return undefined;
};
/**
* @return {number|undefined} Returns the document mode (for testing).
* @private
*/
goog.userAgent.getDocumentMode_ = function() {
// NOTE(user): goog.userAgent may be used in context where there is no DOM.
var doc = goog.global['document'];
return doc ? doc['documentMode'] : undefined;
};
/**
* The version of the user agent. This is a string because it might contain
* 'b' (as in beta) as well as multiple dots.
* @type {string}
*/
goog.userAgent.VERSION = goog.userAgent.determineVersion_();
/**
* Compares two version numbers.
*
* @param {string} v1 Version of first item.
* @param {string} v2 Version of second item.
*
* @return {number} 1 if first argument is higher
* 0 if arguments are equal
* -1 if second argument is higher.
* @deprecated Use goog.string.compareVersions.
*/
goog.userAgent.compare = function(v1, v2) {
return goog.string.compareVersions(v1, v2);
};
/**
* Cache for {@link goog.userAgent.isVersionOrHigher}.
* Calls to compareVersions are surprisingly expensive and, as a browser's
* version number is unlikely to change during a session, we cache the results.
* @const
* @private
*/
goog.userAgent.isVersionOrHigherCache_ = {};
/**
* Whether the user agent version is higher or the same as the given version.
* NOTE: When checking the version numbers for Firefox and Safari, be sure to
* use the engine's version, not the browser's version number. For example,
* Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
* Opera and Internet Explorer versions match the product release number.<br>
* @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
* Webkit</a>
* @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
*
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
*/
goog.userAgent.isVersionOrHigher = function(version) {
return goog.userAgent.ASSUME_ANY_VERSION ||
goog.reflect.cache(
goog.userAgent.isVersionOrHigherCache_, version, function() {
return goog.string.compareVersions(
goog.userAgent.VERSION, version) >= 0;
});
};
/**
* Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
* @deprecated Use goog.userAgent.isVersionOrHigher().
*/
goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
/**
* Whether the IE effective document mode is higher or the same as the given
* document mode version.
* NOTE: Only for IE, return false for another browser.
*
* @param {number} documentMode The document mode version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
*/
goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode;
};
/**
* Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
* @param {number} version The version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
* @deprecated Use goog.userAgent.isDocumentModeOrHigher().
*/
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
/**
* For IE version < 7, documentMode is undefined, so attempt to use the
* CSS1Compat property to see if we are in standards mode. If we are in
* standards mode, treat the browser version as the document mode. Otherwise,
* IE is emulating version 5.
* @type {number|undefined}
* @const
*/
goog.userAgent.DOCUMENT_MODE = (function() {
var doc = goog.global['document'];
var mode = goog.userAgent.getDocumentMode_();
if (!doc || !goog.userAgent.IE) {
return undefined;
}
return mode || (doc['compatMode'] == 'CSS1Compat' ?
parseInt(goog.userAgent.VERSION, 10) :
5);
})();