Updated
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
// 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.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.asserts');
|
||||
goog.provide('goog.asserts.AssertionError');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to strip out asserts or to leave them in.
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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 permenantly 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';
|
||||
|
||||
|
||||
/**
|
||||
* 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).
|
||||
throw new goog.asserts.AssertionError('' + message, args || []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
|
||||
* true.
|
||||
* @param {*} 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 {*} 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.assert.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) {
|
||||
throw 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 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} 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 {!Object}
|
||||
*/
|
||||
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
|
||||
if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
|
||||
goog.asserts.doAssertFailure_('instanceof check failed.', null,
|
||||
opt_message, Array.prototype.slice.call(arguments, 3));
|
||||
}
|
||||
return /** @type {!Object} */(value);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
// 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 A delayed callback that pegs to the next animation frame
|
||||
* instead of a user-configurable timeout.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.async.AnimationDelay');
|
||||
|
||||
goog.require('goog.async.Delay');
|
||||
goog.require('goog.functions');
|
||||
|
||||
|
||||
|
||||
// TODO(nicksantos): Should we factor out the common code between this and
|
||||
// goog.async.Delay? I'm not sure if there's enough code for this to really
|
||||
// make sense. Subclassing seems like the wrong approach for a variety of
|
||||
// reasons. Maybe there should be a common interface?
|
||||
|
||||
/**
|
||||
* A delayed callback that pegs to the next animation frame
|
||||
* instead of a user configurable timeout. By design, this should have
|
||||
* the same interface as goog.async.Delay.
|
||||
*
|
||||
* Uses requestAnimationFrame and friends when available, but falls
|
||||
* back to a timeout of goog.async.AnimationDelay.TIMEOUT.
|
||||
*
|
||||
* For more on requestAnimationFrame and how you can use it to create smoother
|
||||
* animations, see:
|
||||
* @see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
*
|
||||
* @param {function(number)} listener Function to call when the delay completes.
|
||||
* Will be passed the timestamp when it's called, in unix ms.
|
||||
* @param {Window=} opt_window The window object to execute the delay in.
|
||||
* Defaults to the global object.
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.async.AnimationDelay = function(listener, opt_window, opt_handler) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {function(number)}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* @type {Window}
|
||||
* @private
|
||||
*/
|
||||
this.win_ = opt_window || window;
|
||||
|
||||
/**
|
||||
* Cached callback function invoked when the delay finishes.
|
||||
* @type {function()}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.doAction_, this);
|
||||
};
|
||||
goog.inherits(goog.async.AnimationDelay, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Identifier of the active delay timeout, or event listener,
|
||||
* or null when inactive.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.id_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* If we're using dom listeners.
|
||||
* @type {?boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.usingListeners_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Default wait timeout for animations (in milliseconds). Only used for timed
|
||||
* animation, which uses a timer (setTimeout) to schedule animation.
|
||||
*
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.async.AnimationDelay.TIMEOUT = 20;
|
||||
|
||||
|
||||
/**
|
||||
* Name of event received from the requestAnimationFrame in Firefox.
|
||||
*
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_ = 'MozBeforePaint';
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called
|
||||
* before the next animation frame.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.start = function() {
|
||||
this.stop();
|
||||
this.usingListeners_ = false;
|
||||
|
||||
var raf = this.getRaf_();
|
||||
var cancelRaf = this.getCancelRaf_();
|
||||
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
|
||||
// Because Firefox (Gecko) runs animation in separate threads, it also saves
|
||||
// time by running the requestAnimationFrame callbacks in that same thread.
|
||||
// Sadly this breaks the assumption of implicit thread-safety in JS, and can
|
||||
// thus create thread-based inconsistencies on counters etc.
|
||||
//
|
||||
// Calling cycleAnimations_ using the MozBeforePaint event instead of as
|
||||
// callback fixes this.
|
||||
//
|
||||
// Trigger this condition only if the mozRequestAnimationFrame is available,
|
||||
// but not the W3C requestAnimationFrame function (as in draft) or the
|
||||
// equivalent cancel functions.
|
||||
this.id_ = goog.events.listen(
|
||||
this.win_,
|
||||
goog.async.AnimationDelay.MOZ_BEFORE_PAINT_EVENT_,
|
||||
this.callback_);
|
||||
this.win_.mozRequestAnimationFrame(null);
|
||||
this.usingListeners_ = true;
|
||||
} else if (raf && cancelRaf) {
|
||||
this.id_ = raf.call(this.win_, this.callback_);
|
||||
} else {
|
||||
this.id_ = this.win_.setTimeout(
|
||||
// Prior to Firefox 13, Gecko passed a non-standard parameter
|
||||
// to the callback that we want to ignore.
|
||||
goog.functions.lock(this.callback_),
|
||||
goog.async.AnimationDelay.TIMEOUT);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.stop = function() {
|
||||
if (this.isActive()) {
|
||||
var raf = this.getRaf_();
|
||||
var cancelRaf = this.getCancelRaf_();
|
||||
if (raf && !cancelRaf && this.win_.mozRequestAnimationFrame) {
|
||||
goog.events.unlistenByKey(this.id_);
|
||||
} else if (raf && cancelRaf) {
|
||||
cancelRaf.call(this.win_, /** @type {number} */ (this.id_));
|
||||
} else {
|
||||
this.win_.clearTimeout(this.id_);
|
||||
}
|
||||
}
|
||||
this.id_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action even if timer has already gone off or has not been
|
||||
* started yet; guarantees action firing. Stops the delay timer.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.fire = function() {
|
||||
this.stop();
|
||||
this.doAction_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action only if timer is currently active. Stops the delay
|
||||
* timer.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.fireIfActive = function() {
|
||||
if (this.isActive()) {
|
||||
this.fire();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.isActive = function() {
|
||||
return this.id_ != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes the callback function after the delay successfully completes.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.doAction_ = function() {
|
||||
if (this.usingListeners_ && this.id_) {
|
||||
goog.events.unlistenByKey(this.id_);
|
||||
}
|
||||
this.id_ = null;
|
||||
|
||||
// We are not using the timestamp returned by requestAnimationFrame
|
||||
// because it may be either a Date.now-style time or a
|
||||
// high-resolution time (depending on browser implementation). Using
|
||||
// goog.now() will ensure that the timestamp used is consistent and
|
||||
// compatible with goog.fx.Animation.
|
||||
this.listener_.call(this.handler_, goog.now());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.AnimationDelay.prototype.disposeInternal = function() {
|
||||
this.stop();
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?function(function(number)): number} The requestAnimationFrame
|
||||
* function, or null if not available on this browser.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.getRaf_ = function() {
|
||||
var win = this.win_;
|
||||
return win.requestAnimationFrame ||
|
||||
win.webkitRequestAnimationFrame ||
|
||||
win.mozRequestAnimationFrame ||
|
||||
win.oRequestAnimationFrame ||
|
||||
win.msRequestAnimationFrame ||
|
||||
null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?function(number): number} The cancelAnimationFrame function,
|
||||
* or null if not available on this browser.
|
||||
* @private
|
||||
*/
|
||||
goog.async.AnimationDelay.prototype.getCancelRaf_ = function() {
|
||||
var win = this.win_;
|
||||
return win.cancelRequestAnimationFrame ||
|
||||
win.webkitCancelRequestAnimationFrame ||
|
||||
win.mozCancelRequestAnimationFrame ||
|
||||
win.oCancelRequestAnimationFrame ||
|
||||
win.msCancelRequestAnimationFrame ||
|
||||
null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
// 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 Defines a class useful for handling functions that must be
|
||||
* invoked later when some condition holds. Examples include deferred function
|
||||
* calls that return a boolean flag whether it succedeed or not.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* function deferred() {
|
||||
* var succeeded = false;
|
||||
* // ... custom code
|
||||
* return succeeded;
|
||||
* }
|
||||
*
|
||||
* var deferredCall = new goog.async.ConditionalDelay(deferred);
|
||||
* deferredCall.onSuccess = function() {
|
||||
* alert('Success: The deferred function has been successfully executed.');
|
||||
* }
|
||||
* deferredCall.onFailure = function() {
|
||||
* alert('Failure: Time limit exceeded.');
|
||||
* }
|
||||
*
|
||||
* // Call the deferred() every 100 msec until it returns true,
|
||||
* // or 5 seconds pass.
|
||||
* deferredCall.start(100, 5000);
|
||||
*
|
||||
* // Stop the deferred function call (does nothing if it's not active).
|
||||
* deferredCall.stop();
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.async.ConditionalDelay');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.async.Delay');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A ConditionalDelay object invokes the associated function after a specified
|
||||
* interval delay and checks its return value. If the function returns
|
||||
* {@code true} the conditional delay is cancelled and {@see #onSuccess}
|
||||
* is called. Otherwise this object keeps to invoke the deferred function until
|
||||
* either it returns {@code true} or the timeout is exceeded. In the latter case
|
||||
* the {@see #onFailure} method will be called.
|
||||
*
|
||||
* The interval duration and timeout can be specified each time the delay is
|
||||
* started. Calling start on an active delay will reset the timer.
|
||||
*
|
||||
* @param {function():boolean} listener Function to call when the delay
|
||||
* completes. Should return a value that type-converts to {@code true} if
|
||||
* the call succeeded and this delay should be stopped.
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.async.ConditionalDelay = function(listener, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {function():boolean}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* The underlying goog.async.Delay delegate object.
|
||||
* @type {goog.async.Delay}
|
||||
* @private
|
||||
*/
|
||||
this.delay_ = new goog.async.Delay(
|
||||
goog.bind(this.onTick_, this), 0 /*interval*/, this /*scope*/);
|
||||
};
|
||||
goog.inherits(goog.async.ConditionalDelay, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* The delay interval in milliseconds to between the calls to the callback.
|
||||
* Note, that the callback may be invoked earlier than this interval if the
|
||||
* timeout is exceeded.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.interval_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The timeout timestamp until which the delay is to be executed.
|
||||
* A negative value means no timeout.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.runUntil_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* True if the listener has been executed, and it returned {@code true}.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isDone_ = false;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.ConditionalDelay.prototype.disposeInternal = function() {
|
||||
this.delay_.dispose();
|
||||
delete this.listener_;
|
||||
delete this.handler_;
|
||||
goog.async.ConditionalDelay.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called
|
||||
* repeatedly after the specified interval until the function returns
|
||||
* {@code true} or the timeout is exceeded. Calling start on an active timer
|
||||
* will stop the timer first.
|
||||
* @param {number=} opt_interval The time interval between the function
|
||||
* invocations (in milliseconds). Default is 0.
|
||||
* @param {number=} opt_timeout The timeout interval (in milliseconds). Takes
|
||||
* precedence over the {@code opt_interval}, i.e. if the timeout is less
|
||||
* than the invocation interval, the function will be called when the
|
||||
* timeout is exceeded. A negative value means no timeout. Default is 0.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.start = function(opt_interval,
|
||||
opt_timeout) {
|
||||
this.stop();
|
||||
this.isDone_ = false;
|
||||
|
||||
var timeout = opt_timeout || 0;
|
||||
this.interval_ = Math.max(opt_interval || 0, 0);
|
||||
this.runUntil_ = timeout < 0 ? -1 : (goog.now() + timeout);
|
||||
this.delay_.start(
|
||||
timeout < 0 ? this.interval_ : Math.min(this.interval_, timeout));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.stop = function() {
|
||||
this.delay_.stop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isActive = function() {
|
||||
return this.delay_.isActive();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the listener has been executed and returned
|
||||
* {@code true} since the last call to {@see #start}.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.isDone = function() {
|
||||
return this.isDone_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when the listener has been successfully executed and returned
|
||||
* {@code true}. The {@see #isDone} method should return {@code true} by now.
|
||||
* Designed for inheritance, should be overridden by subclasses or on the
|
||||
* instances if they care.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onSuccess = function() {
|
||||
// Do nothing by default.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when this delayed call is cancelled because the timeout has been
|
||||
* exceeded, and the listener has never returned {@code true}.
|
||||
* Designed for inheritance, should be overridden by subclasses or on the
|
||||
* instances if they care.
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onFailure = function() {
|
||||
// Do nothing by default.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback function for the underlying {@code goog.async.Delay} object. When
|
||||
* executed the listener function is called, and if it returns {@code true}
|
||||
* the delay is stopped and the {@see #onSuccess} method is invoked.
|
||||
* If the timeout is exceeded the delay is stopped and the
|
||||
* {@see #onFailure} method is called.
|
||||
* @private
|
||||
*/
|
||||
goog.async.ConditionalDelay.prototype.onTick_ = function() {
|
||||
var successful = this.listener_.call(this.handler_);
|
||||
if (successful) {
|
||||
this.isDone_ = true;
|
||||
this.onSuccess();
|
||||
} else {
|
||||
// Try to reschedule the task.
|
||||
if (this.runUntil_ < 0) {
|
||||
// No timeout.
|
||||
this.delay_.start(this.interval_);
|
||||
} else {
|
||||
var timeLeft = this.runUntil_ - goog.now();
|
||||
if (timeLeft <= 0) {
|
||||
this.onFailure();
|
||||
} else {
|
||||
this.delay_.start(Math.min(this.interval_, timeLeft));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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 a class useful for handling functions that must be
|
||||
* invoked after a delay, especially when that delay is frequently restarted.
|
||||
* Examples include delaying before displaying a tooltip, menu hysteresis,
|
||||
* idle timers, etc.
|
||||
* @author brenneman@google.com (Shawn Brenneman)
|
||||
* @see ../demos/timers.html
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.Delay');
|
||||
goog.provide('goog.async.Delay');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Timer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A Delay object invokes the associated function after a specified delay. The
|
||||
* interval duration can be specified once in the constructor, or can be defined
|
||||
* each time the delay is started. Calling start on an active delay will reset
|
||||
* the timer.
|
||||
*
|
||||
* @param {Function} listener Function to call when the delay completes.
|
||||
* @param {number=} opt_interval The default length of the invocation delay (in
|
||||
* milliseconds).
|
||||
* @param {Object=} opt_handler The object scope to invoke the function in.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.async.Delay = function(listener, opt_interval, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The function that will be invoked after a delay.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* The default amount of time to delay before invoking the callback.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.interval_ = opt_interval || 0;
|
||||
|
||||
/**
|
||||
* The object context to invoke the callback in.
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
|
||||
/**
|
||||
* Cached callback function invoked when the delay finishes.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.doAction_, this);
|
||||
};
|
||||
goog.inherits(goog.async.Delay, goog.Disposable);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A deprecated alias.
|
||||
* @deprecated Use goog.async.Delay instead.
|
||||
* @constructor
|
||||
*/
|
||||
goog.Delay = goog.async.Delay;
|
||||
|
||||
|
||||
/**
|
||||
* Identifier of the active delay timeout, or 0 when inactive.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Delay.prototype.id_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the object, cancelling the timeout if it is still outstanding and
|
||||
* removing all object references.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.async.Delay.prototype.disposeInternal = function() {
|
||||
goog.async.Delay.superClass_.disposeInternal.call(this);
|
||||
this.stop();
|
||||
delete this.listener_;
|
||||
delete this.handler_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the delay timer. The provided listener function will be called after
|
||||
* the specified interval. Calling start on an active timer will reset the
|
||||
* delay interval.
|
||||
* @param {number=} opt_interval If specified, overrides the object's default
|
||||
* interval with this one (in milliseconds).
|
||||
*/
|
||||
goog.async.Delay.prototype.start = function(opt_interval) {
|
||||
this.stop();
|
||||
this.id_ = goog.Timer.callOnce(
|
||||
this.callback_,
|
||||
goog.isDef(opt_interval) ? opt_interval : this.interval_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops the delay timer if it is active. No action is taken if the timer is not
|
||||
* in use.
|
||||
*/
|
||||
goog.async.Delay.prototype.stop = function() {
|
||||
if (this.isActive()) {
|
||||
goog.Timer.clear(this.id_);
|
||||
}
|
||||
this.id_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action even if timer has already gone off or has not been
|
||||
* started yet; guarantees action firing. Stops the delay timer.
|
||||
*/
|
||||
goog.async.Delay.prototype.fire = function() {
|
||||
this.stop();
|
||||
this.doAction_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fires delay's action only if timer is currently active. Stops the delay
|
||||
* timer.
|
||||
*/
|
||||
goog.async.Delay.prototype.fireIfActive = function() {
|
||||
if (this.isActive()) {
|
||||
this.fire();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the delay is currently active, false otherwise.
|
||||
*/
|
||||
goog.async.Delay.prototype.isActive = function() {
|
||||
return this.id_ != 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes the callback function after the delay successfully completes.
|
||||
* @private
|
||||
*/
|
||||
goog.async.Delay.prototype.doAction_ = function() {
|
||||
this.id_ = 0;
|
||||
if (this.listener_) {
|
||||
this.listener_.call(this.handler_);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the goog.async.Throttle class.
|
||||
*
|
||||
* @see ../demos/timers.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.Throttle');
|
||||
goog.provide('goog.async.Throttle');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Timer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Throttle will perform an action that is passed in no more than once
|
||||
* per interval (specified in milliseconds). If it gets multiple signals
|
||||
* to perform the action while it is waiting, it will only perform the action
|
||||
* once at the end of the interval.
|
||||
* @param {Function} listener Function to callback when the action is triggered.
|
||||
* @param {number} interval Interval over which to throttle. The handler can
|
||||
* only be called once per interval.
|
||||
* @param {Object=} opt_handler Object in whose scope to call the listener.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.async.Throttle = function(listener, interval, opt_handler) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* Function to callback
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.listener_ = listener;
|
||||
|
||||
/**
|
||||
* Interval for the throttle time
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.interval_ = interval;
|
||||
|
||||
/**
|
||||
* "this" context for the listener
|
||||
* @type {Object|undefined}
|
||||
* @private
|
||||
*/
|
||||
this.handler_ = opt_handler;
|
||||
|
||||
/**
|
||||
* Cached callback function invoked after the throttle timeout completes
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = goog.bind(this.onTimer_, this);
|
||||
};
|
||||
goog.inherits(goog.async.Throttle, goog.Disposable);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A deprecated alias.
|
||||
* @deprecated Use goog.async.Throttle instead.
|
||||
* @constructor
|
||||
*/
|
||||
goog.Throttle = goog.async.Throttle;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the action is pending and needs to be fired.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.shouldFire_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the count of nested pauses currently in effect on the throttle.
|
||||
* When this count is not zero, fired actions will be postponed until the
|
||||
* throttle is resumed enough times to drop the pause count to zero.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.pauseCount_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Timer for scheduling the next callback
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.timer_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the throttle that the action has happened. It will throttle the call
|
||||
* so that the callback is not called too often according to the interval
|
||||
* parameter passed to the constructor.
|
||||
*/
|
||||
goog.async.Throttle.prototype.fire = function() {
|
||||
if (!this.timer_ && !this.pauseCount_) {
|
||||
this.doAction_();
|
||||
} else {
|
||||
this.shouldFire_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cancels any pending action callback. The throttle can be restarted by
|
||||
* calling {@link #fire}.
|
||||
*/
|
||||
goog.async.Throttle.prototype.stop = function() {
|
||||
if (this.timer_) {
|
||||
goog.Timer.clear(this.timer_);
|
||||
this.timer_ = null;
|
||||
this.shouldFire_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pauses the throttle. All pending and future action callbacks will be
|
||||
* delayed until the throttle is resumed. Pauses can be nested.
|
||||
*/
|
||||
goog.async.Throttle.prototype.pause = function() {
|
||||
this.pauseCount_++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resumes the throttle. If doing so drops the pausing count to zero, pending
|
||||
* action callbacks will be executed as soon as possible, but still no sooner
|
||||
* than an interval's delay after the previous call. Future action callbacks
|
||||
* will be executed as normal.
|
||||
*/
|
||||
goog.async.Throttle.prototype.resume = function() {
|
||||
this.pauseCount_--;
|
||||
if (!this.pauseCount_ && this.shouldFire_ && !this.timer_) {
|
||||
this.shouldFire_ = false;
|
||||
this.doAction_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.async.Throttle.prototype.disposeInternal = function() {
|
||||
goog.async.Throttle.superClass_.disposeInternal.call(this);
|
||||
this.stop();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the timer to fire the throttle
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.onTimer_ = function() {
|
||||
this.timer_ = null;
|
||||
|
||||
if (this.shouldFire_ && !this.pauseCount_) {
|
||||
this.shouldFire_ = false;
|
||||
this.doAction_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the callback
|
||||
* @private
|
||||
*/
|
||||
goog.async.Throttle.prototype.doAction_ = function() {
|
||||
this.timer_ = goog.Timer.callOnce(this.callback_, this.interval_);
|
||||
this.listener_.call(this.handler_);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
// 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 bootstrap for dynamically requiring Closure within an HTML5
|
||||
* Web Worker context. To use this, first set CLOSURE_BASE_PATH to the directory
|
||||
* containing base.js (relative to the main script), then use importScripts to
|
||||
* load this file and base.js (in that order). After this you can use
|
||||
* goog.require for further imports.
|
||||
*
|
||||
* @nocompile
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Imports a script using the Web Worker importScript API.
|
||||
*
|
||||
* @param {string} src The script source.
|
||||
* @return {boolean} True if the script was imported, false otherwise.
|
||||
*/
|
||||
this.CLOSURE_IMPORT_SCRIPT = (function(global) {
|
||||
return function(src) {
|
||||
global['importScripts'](src);
|
||||
return true;
|
||||
};
|
||||
})(this);
|
||||
@@ -0,0 +1,467 @@
|
||||
// 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 related to alpha/transparent colors and alpha color
|
||||
* conversion.
|
||||
*/
|
||||
|
||||
goog.provide('goog.color.alpha');
|
||||
|
||||
goog.require('goog.color');
|
||||
|
||||
|
||||
/**
|
||||
* Parses an alpha color out of a string.
|
||||
* @param {string} str Color in some format.
|
||||
* @return {Object} Contains two properties: 'hex', which is a string containing
|
||||
* a hex representation of the color, as well as 'type', which is a string
|
||||
* containing the type of color format passed in ('hex', 'rgb', 'named').
|
||||
*/
|
||||
goog.color.alpha.parse = function(str) {
|
||||
var result = {};
|
||||
str = String(str);
|
||||
|
||||
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
|
||||
if (goog.color.alpha.isValidAlphaHexColor_(maybeHex)) {
|
||||
result.hex = goog.color.alpha.normalizeAlphaHex_(maybeHex);
|
||||
result.type = 'hex';
|
||||
return result;
|
||||
} else {
|
||||
var rgba = goog.color.alpha.isValidRgbaColor_(str);
|
||||
if (rgba.length) {
|
||||
result.hex = goog.color.alpha.rgbaArrayToHex(rgba);
|
||||
result.type = 'rgba';
|
||||
return result;
|
||||
} else {
|
||||
var hsla = goog.color.alpha.isValidHslaColor_(str);
|
||||
if (hsla.length) {
|
||||
result.hex = goog.color.alpha.hslaArrayToHex(hsla);
|
||||
result.type = 'hsla';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error(str + ' is not a valid color string');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a hex representation of a color to RGBA.
|
||||
* @param {string} hexColor Color to convert.
|
||||
* @return {string} string of the form 'rgba(R,G,B,A)' which can be used in
|
||||
* styles.
|
||||
*/
|
||||
goog.color.alpha.hexToRgbaStyle = function(hexColor) {
|
||||
return goog.color.alpha.rgbaStyle_(goog.color.alpha.hexToRgba(hexColor));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the hex color part of an alpha hex color. For example, from '#abcdef55'
|
||||
* return '#abcdef'.
|
||||
* @param {string} colorWithAlpha The alpha hex color to get the hex color from.
|
||||
* @return {string} The hex color where the alpha part has been stripped off.
|
||||
*/
|
||||
goog.color.alpha.extractHexColor = function(colorWithAlpha) {
|
||||
if (goog.color.alpha.isValidAlphaHexColor_(colorWithAlpha)) {
|
||||
var fullColor = goog.color.prependHashIfNecessaryHelper(colorWithAlpha);
|
||||
var normalizedColor = goog.color.alpha.normalizeAlphaHex_(fullColor);
|
||||
return normalizedColor.substring(0, 7);
|
||||
} else {
|
||||
throw Error(colorWithAlpha + ' is not a valid 8-hex color string');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the alpha color part of an alpha hex color. For example, from
|
||||
* '#abcdef55' return '55'. The result is guaranteed to be two characters long.
|
||||
* @param {string} colorWithAlpha The alpha hex color to get the hex color from.
|
||||
* @return {string} The hex color where the alpha part has been stripped off.
|
||||
*/
|
||||
goog.color.alpha.extractAlpha = function(colorWithAlpha) {
|
||||
if (goog.color.alpha.isValidAlphaHexColor_(colorWithAlpha)) {
|
||||
var fullColor = goog.color.prependHashIfNecessaryHelper(colorWithAlpha);
|
||||
var normalizedColor = goog.color.alpha.normalizeAlphaHex_(fullColor);
|
||||
return normalizedColor.substring(7, 9);
|
||||
} else {
|
||||
throw Error(colorWithAlpha + ' is not a valid 8-hex color string');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for extracting the digits in a hex color quadruplet.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.hexQuadrupletRe_ = /#(.)(.)(.)(.)/;
|
||||
|
||||
|
||||
/**
|
||||
* Normalize a hex representation of an alpha color.
|
||||
* @param {string} hexColor an alpha hex color string.
|
||||
* @return {string} hex color in the format '#rrggbbaa' with all lowercase
|
||||
* literals.
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.normalizeAlphaHex_ = function(hexColor) {
|
||||
if (!goog.color.alpha.isValidAlphaHexColor_(hexColor)) {
|
||||
throw Error("'" + hexColor + "' is not a valid alpha hex color");
|
||||
}
|
||||
if (hexColor.length == 5) { // of the form #RGBA
|
||||
hexColor = hexColor.replace(goog.color.alpha.hexQuadrupletRe_,
|
||||
'#$1$1$2$2$3$3$4$4');
|
||||
}
|
||||
return hexColor.toLowerCase();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an 8-hex representation of a color to RGBA.
|
||||
* @param {string} hexColor Color to convert.
|
||||
* @return {Array} array containing [r, g, b] as ints in [0, 255].
|
||||
*/
|
||||
goog.color.alpha.hexToRgba = function(hexColor) {
|
||||
// TODO(user): Enhance code sharing with goog.color, for example by
|
||||
// adding a goog.color.genericHexToRgb method.
|
||||
hexColor = goog.color.alpha.normalizeAlphaHex_(hexColor);
|
||||
var r = parseInt(hexColor.substr(1, 2), 16);
|
||||
var g = parseInt(hexColor.substr(3, 2), 16);
|
||||
var b = parseInt(hexColor.substr(5, 2), 16);
|
||||
var a = parseInt(hexColor.substr(7, 2), 16);
|
||||
|
||||
return [r, g, b, a / 255];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA to hex representation.
|
||||
* @param {number} r Amount of red, int between 0 and 255.
|
||||
* @param {number} g Amount of green, int between 0 and 255.
|
||||
* @param {number} b Amount of blue, int between 0 and 255.
|
||||
* @param {number} a Amount of alpha, float between 0 and 1.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.alpha.rgbaToHex = function(r, g, b, a) {
|
||||
var intAlpha = Math.floor(a * 255);
|
||||
if (isNaN(intAlpha) || intAlpha < 0 || intAlpha > 255) {
|
||||
// TODO(user): The CSS spec says the value should be clamped.
|
||||
throw Error('"(' + r + ',' + g + ',' + b + ',' + a +
|
||||
'") is not a valid RGBA color');
|
||||
}
|
||||
var hexA = goog.color.prependZeroIfNecessaryHelper(intAlpha.toString(16));
|
||||
return goog.color.rgbToHex(r, g, b) + hexA;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSLA to hex representation.
|
||||
* @param {number} h Amount of hue, int between 0 and 360.
|
||||
* @param {number} s Amount of saturation, int between 0 and 100.
|
||||
* @param {number} l Amount of lightness, int between 0 and 100.
|
||||
* @param {number} a Amount of alpha, float between 0 and 1.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.alpha.hslaToHex = function(h, s, l, a) {
|
||||
var intAlpha = Math.floor(a * 255);
|
||||
if (isNaN(intAlpha) || intAlpha < 0 || intAlpha > 255) {
|
||||
// TODO(user): The CSS spec says the value should be clamped.
|
||||
throw Error('"(' + h + ',' + s + ',' + l + ',' + a +
|
||||
'") is not a valid HSLA color');
|
||||
}
|
||||
var hexA = goog.color.prependZeroIfNecessaryHelper(intAlpha.toString(16));
|
||||
return goog.color.hslToHex(h, s / 100, l / 100) + hexA;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA to hex representation.
|
||||
* @param {Array.<number>} rgba Array of [r, g, b, a], with r, g, b in [0, 255]
|
||||
* and a in [0, 1].
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.alpha.rgbaArrayToHex = function(rgba) {
|
||||
return goog.color.alpha.rgbaToHex(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA to an RGBA style string.
|
||||
* @param {number} r Value of red, in [0, 255].
|
||||
* @param {number} g Value of green, in [0, 255].
|
||||
* @param {number} b Value of blue, in [0, 255].
|
||||
* @param {number} a Value of alpha, in [0, 1].
|
||||
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
|
||||
*/
|
||||
goog.color.alpha.rgbaToRgbaStyle = function(r, g, b, a) {
|
||||
if (isNaN(r) || r < 0 || r > 255 ||
|
||||
isNaN(g) || g < 0 || g > 255 ||
|
||||
isNaN(b) || b < 0 || b > 255 ||
|
||||
isNaN(a) || a < 0 || a > 1) {
|
||||
throw Error('"(' + r + ',' + g + ',' + b + ',' + a +
|
||||
')" is not a valid RGBA color');
|
||||
}
|
||||
return goog.color.alpha.rgbaStyle_([r, g, b, a]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA to an RGBA style string.
|
||||
* @param {(Array.<number>|Float32Array)} rgba Array of [r, g, b, a],
|
||||
* with r, g, b in [0, 255] and a in [0, 1].
|
||||
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
|
||||
*/
|
||||
goog.color.alpha.rgbaArrayToRgbaStyle = function(rgba) {
|
||||
return goog.color.alpha.rgbaToRgbaStyle(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSLA to hex representation.
|
||||
* @param {Array.<number>} hsla Array of [h, s, l, a], where h is an integer in
|
||||
* [0, 360], s and l are integers in [0, 100], and a is in [0, 1].
|
||||
* @return {string} hex representation of the color, such as '#af457eff'.
|
||||
*/
|
||||
goog.color.alpha.hslaArrayToHex = function(hsla) {
|
||||
return goog.color.alpha.hslaToHex(hsla[0], hsla[1], hsla[2], hsla[3]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSLA to an RGBA style string.
|
||||
* @param {Array.<number>} hsla Array of [h, s, l, a], where h is and integer in
|
||||
* [0, 360], s and l are integers in [0, 100], and a is in [0, 1].
|
||||
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
|
||||
*/
|
||||
goog.color.alpha.hslaArrayToRgbaStyle = function(hsla) {
|
||||
return goog.color.alpha.hslaToRgbaStyle(hsla[0], hsla[1], hsla[2], hsla[3]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSLA to an RGBA style string.
|
||||
* @param {number} h Amount of hue, int between 0 and 360.
|
||||
* @param {number} s Amount of saturation, int between 0 and 100.
|
||||
* @param {number} l Amount of lightness, int between 0 and 100.
|
||||
* @param {number} a Amount of alpha, float between 0 and 1.
|
||||
* @return {string} An 'rgba(r,g,b,a)' string ready for use in a CSS rule.
|
||||
* styles.
|
||||
*/
|
||||
goog.color.alpha.hslaToRgbaStyle = function(h, s, l, a) {
|
||||
return goog.color.alpha.rgbaStyle_(goog.color.alpha.hslaToRgba(h, s, l, a));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSLA color space to RGBA color space.
|
||||
* @param {number} h Amount of hue, int between 0 and 360.
|
||||
* @param {number} s Amount of saturation, int between 0 and 100.
|
||||
* @param {number} l Amount of lightness, int between 0 and 100.
|
||||
* @param {number} a Amount of alpha, float between 0 and 1.
|
||||
* @return {Array.<number>} [r, g, b, a] values for the color, where r, g, b
|
||||
* are integers in [0, 255] and a is a float in [0, 1].
|
||||
*/
|
||||
goog.color.alpha.hslaToRgba = function(h, s, l, a) {
|
||||
return goog.color.hslToRgb(h, s / 100, l / 100).concat(a);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA color space to HSLA color space.
|
||||
* Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}.
|
||||
* @param {number} r Value of red, in [0, 255].
|
||||
* @param {number} g Value of green, in [0, 255].
|
||||
* @param {number} b Value of blue, in [0, 255].
|
||||
* @param {number} a Value of alpha, in [0, 255].
|
||||
* @return {Array.<number>} [h, s, l, a] values for the color, with h an int in
|
||||
* [0, 360] and s, l and a in [0, 1].
|
||||
*/
|
||||
goog.color.alpha.rgbaToHsla = function(r, g, b, a) {
|
||||
return goog.color.rgbToHsl(r, g, b).concat(a);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGBA color space to HSLA color space.
|
||||
* @param {Array.<number>} rgba [r, g, b, a] values for the color, each in
|
||||
* [0, 255].
|
||||
* @return {Array.<number>} [h, s, l, a] values for the color, with h in
|
||||
* [0, 360] and s, l and a in [0, 1].
|
||||
*/
|
||||
goog.color.alpha.rgbaArrayToHsla = function(rgba) {
|
||||
return goog.color.alpha.rgbaToHsla(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for isValidAlphaHexColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.validAlphaHexColorRe_ = /^#(?:[0-9a-f]{4}){1,2}$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid alpha hex color. We expect strings of the
|
||||
* format #RRGGBBAA (ex: #1b3d5f5b) or #RGBA (ex: #3CAF == #33CCAAFF).
|
||||
* @param {string} str String to check.
|
||||
* @return {boolean} Whether the string is a valid alpha hex color.
|
||||
* @private
|
||||
*/
|
||||
// TODO(user): Support percentages when goog.color also supports them.
|
||||
goog.color.alpha.isValidAlphaHexColor_ = function(str) {
|
||||
return goog.color.alpha.validAlphaHexColorRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for isNormalizedAlphaHexColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.normalizedAlphaHexColorRe_ = /^#[0-9a-f]{8}$/;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a normalized alpha hex color.
|
||||
* We expect strings of the format #RRGGBBAA (ex: #1b3d5f5b)
|
||||
* using only lowercase letters.
|
||||
* @param {string} str String to check.
|
||||
* @return {boolean} Whether the string is a normalized hex color.
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.isNormalizedAlphaHexColor_ = function(str) {
|
||||
return goog.color.alpha.normalizedAlphaHexColorRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for matching and capturing RGBA style strings. Helper for
|
||||
* isValidRgbaColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.rgbaColorRe_ =
|
||||
/^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for matching and capturing HSLA style strings. Helper for
|
||||
* isValidHslaColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.hslaColorRe_ =
|
||||
/^(?:hsla)\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\%,\s?(0|[1-9]\d{0,2})\%,\s?(0|1|0\.\d{0,10})\)$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid rgba color. We expect strings of the format
|
||||
* '(r, g, b, a)', or 'rgba(r, g, b, a)', where r, g, b are ints in [0, 255]
|
||||
* and a is a float in [0, 1].
|
||||
* @param {string} str String to check.
|
||||
* @return {Array.<number>} the integers [r, g, b, a] for valid colors or the
|
||||
* empty array for invalid colors.
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.isValidRgbaColor_ = function(str) {
|
||||
// Each component is separate (rather than using a repeater) so we can
|
||||
// capture the match. Also, we explicitly set each component to be either 0,
|
||||
// or start with a non-zero, to prevent octal numbers from slipping through.
|
||||
var regExpResultArray = str.match(goog.color.alpha.rgbaColorRe_);
|
||||
if (regExpResultArray) {
|
||||
var r = Number(regExpResultArray[1]);
|
||||
var g = Number(regExpResultArray[2]);
|
||||
var b = Number(regExpResultArray[3]);
|
||||
var a = Number(regExpResultArray[4]);
|
||||
if (r >= 0 && r <= 255 &&
|
||||
g >= 0 && g <= 255 &&
|
||||
b >= 0 && b <= 255 &&
|
||||
a >= 0 && a <= 1) {
|
||||
return [r, g, b, a];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid hsla color. We expect strings of the format
|
||||
* 'hsla(h, s, l, a)', where s in an int in [0, 360], s and l are percentages
|
||||
* between 0 and 100 such as '50%' or '70%', and a is a float in [0, 1].
|
||||
* @param {string} str String to check.
|
||||
* @return {Array.<number>} the integers [h, s, l, a] for valid colors or the
|
||||
* empty array for invalid colors.
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.isValidHslaColor_ = function(str) {
|
||||
// Each component is separate (rather than using a repeater) so we can
|
||||
// capture the match. Also, we explicitly set each component to be either 0,
|
||||
// or start with a non-zero, to prevent octal numbers from slipping through.
|
||||
var regExpResultArray = str.match(goog.color.alpha.hslaColorRe_);
|
||||
if (regExpResultArray) {
|
||||
var h = Number(regExpResultArray[1]);
|
||||
var s = Number(regExpResultArray[2]);
|
||||
var l = Number(regExpResultArray[3]);
|
||||
var a = Number(regExpResultArray[4]);
|
||||
if (h >= 0 && h <= 360 &&
|
||||
s >= 0 && s <= 100 &&
|
||||
l >= 0 && l <= 100 &&
|
||||
a >= 0 && a <= 1) {
|
||||
return [h, s, l, a];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes an array of [r, g, b, a] and converts it into a string appropriate for
|
||||
* CSS styles.
|
||||
* @param {Array.<number>} rgba [r, g, b, a] with r, g, b in [0, 255] and a
|
||||
* in [0, 1].
|
||||
* @return {string} string of the form 'rgba(r,g,b,a)'.
|
||||
* @private
|
||||
*/
|
||||
goog.color.alpha.rgbaStyle_ = function(rgba) {
|
||||
return 'rgba(' + rgba.join(',') + ')';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from h,s,v,a values to a hex string
|
||||
* @param {number} h Hue, in [0, 1].
|
||||
* @param {number} s Saturation, in [0, 1].
|
||||
* @param {number} v Value, in [0, 255].
|
||||
* @param {number} a Alpha, in [0, 1].
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.alpha.hsvaToHex = function(h, s, v, a) {
|
||||
var alpha = Math.floor(a * 255);
|
||||
return goog.color.hsvArrayToHex([h, s, v]) +
|
||||
goog.color.prependZeroIfNecessaryHelper(alpha.toString(16));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from an HSVA array to a hex string
|
||||
* @param {Array} hsva Array of [h, s, v, a] in
|
||||
* [[0, 1], [0, 1], [0, 255], [0, 1]].
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.alpha.hsvaArrayToHex = function(hsva) {
|
||||
return goog.color.alpha.hsvaToHex(hsva[0], hsva[1], hsva[2], hsva[3]);
|
||||
};
|
||||
@@ -0,0 +1,773 @@
|
||||
// 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 related to color and color conversion.
|
||||
*/
|
||||
|
||||
goog.provide('goog.color');
|
||||
|
||||
goog.require('goog.color.names');
|
||||
goog.require('goog.math');
|
||||
|
||||
|
||||
/**
|
||||
* RGB color representation. An array containing three elements [r, g, b],
|
||||
* each an integer in [0, 255], representing the red, green, and blue components
|
||||
* of the color respectively.
|
||||
* @typedef {Array.<number>}
|
||||
*/
|
||||
goog.color.Rgb;
|
||||
|
||||
|
||||
/**
|
||||
* HSV color representation. An array containing three elements [h, s, v]:
|
||||
* h (hue) must be an integer in [0, 360], cyclic.
|
||||
* s (saturation) must be a number in [0, 1].
|
||||
* v (value/brightness) must be an integer in [0, 255].
|
||||
* @typedef {Array.<number>}
|
||||
*/
|
||||
goog.color.Hsv;
|
||||
|
||||
|
||||
/**
|
||||
* HSL color representation. An array containing three elements [h, s, l]:
|
||||
* h (hue) must be an integer in [0, 360], cyclic.
|
||||
* s (saturation) must be a number in [0, 1].
|
||||
* l (lightness) must be a number in [0, 1].
|
||||
* @typedef {Array.<number>}
|
||||
*/
|
||||
goog.color.Hsl;
|
||||
|
||||
|
||||
/**
|
||||
* Parses a color out of a string.
|
||||
* @param {string} str Color in some format.
|
||||
* @return {Object} Contains two properties: 'hex', which is a string containing
|
||||
* a hex representation of the color, as well as 'type', which is a string
|
||||
* containing the type of color format passed in ('hex', 'rgb', 'named').
|
||||
*/
|
||||
goog.color.parse = function(str) {
|
||||
var result = {};
|
||||
str = String(str);
|
||||
|
||||
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
|
||||
if (goog.color.isValidHexColor_(maybeHex)) {
|
||||
result.hex = goog.color.normalizeHex(maybeHex);
|
||||
result.type = 'hex';
|
||||
return result;
|
||||
} else {
|
||||
var rgb = goog.color.isValidRgbColor_(str);
|
||||
if (rgb.length) {
|
||||
result.hex = goog.color.rgbArrayToHex(rgb);
|
||||
result.type = 'rgb';
|
||||
return result;
|
||||
} else if (goog.color.names) {
|
||||
var hex = goog.color.names[str.toLowerCase()];
|
||||
if (hex) {
|
||||
result.hex = hex;
|
||||
result.type = 'named';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw Error(str + ' is not a valid color string');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the given string can be parsed as a color.
|
||||
* {@see goog.color.parse}.
|
||||
* @param {string} str Potential color string.
|
||||
* @return {boolean} True if str is in a format that can be parsed to a color.
|
||||
*/
|
||||
goog.color.isValidColor = function(str) {
|
||||
var maybeHex = goog.color.prependHashIfNecessaryHelper(str);
|
||||
return !!(goog.color.isValidHexColor_(maybeHex) ||
|
||||
goog.color.isValidRgbColor_(str).length ||
|
||||
goog.color.names && goog.color.names[str.toLowerCase()]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses red, green, blue components out of a valid rgb color string.
|
||||
* Throws Error if the color string is invalid.
|
||||
* @param {string} str RGB representation of a color.
|
||||
* {@see goog.color.isValidRgbColor_}.
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.parseRgb = function(str) {
|
||||
var rgb = goog.color.isValidRgbColor_(str);
|
||||
if (!rgb.length) {
|
||||
throw Error(str + ' is not a valid RGB color');
|
||||
}
|
||||
return rgb;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a hex representation of a color to RGB.
|
||||
* @param {string} hexColor Color to convert.
|
||||
* @return {string} string of the form 'rgb(R,G,B)' which can be used in
|
||||
* styles.
|
||||
*/
|
||||
goog.color.hexToRgbStyle = function(hexColor) {
|
||||
return goog.color.rgbStyle_(goog.color.hexToRgb(hexColor));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for extracting the digits in a hex color triplet.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.hexTripletRe_ = /#(.)(.)(.)/;
|
||||
|
||||
|
||||
/**
|
||||
* Normalize an hex representation of a color
|
||||
* @param {string} hexColor an hex color string.
|
||||
* @return {string} hex color in the format '#rrggbb' with all lowercase
|
||||
* literals.
|
||||
*/
|
||||
goog.color.normalizeHex = function(hexColor) {
|
||||
if (!goog.color.isValidHexColor_(hexColor)) {
|
||||
throw Error("'" + hexColor + "' is not a valid hex color");
|
||||
}
|
||||
if (hexColor.length == 4) { // of the form #RGB
|
||||
hexColor = hexColor.replace(goog.color.hexTripletRe_, '#$1$1$2$2$3$3');
|
||||
}
|
||||
return hexColor.toLowerCase();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a hex representation of a color to RGB.
|
||||
* @param {string} hexColor Color to convert.
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.hexToRgb = function(hexColor) {
|
||||
hexColor = goog.color.normalizeHex(hexColor);
|
||||
var r = parseInt(hexColor.substr(1, 2), 16);
|
||||
var g = parseInt(hexColor.substr(3, 2), 16);
|
||||
var b = parseInt(hexColor.substr(5, 2), 16);
|
||||
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGB to hex representation.
|
||||
* @param {number} r Amount of red, int between 0 and 255.
|
||||
* @param {number} g Amount of green, int between 0 and 255.
|
||||
* @param {number} b Amount of blue, int between 0 and 255.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.rgbToHex = function(r, g, b) {
|
||||
r = Number(r);
|
||||
g = Number(g);
|
||||
b = Number(b);
|
||||
if (isNaN(r) || r < 0 || r > 255 ||
|
||||
isNaN(g) || g < 0 || g > 255 ||
|
||||
isNaN(b) || b < 0 || b > 255) {
|
||||
throw Error('"(' + r + ',' + g + ',' + b + '") is not a valid RGB color');
|
||||
}
|
||||
var hexR = goog.color.prependZeroIfNecessaryHelper(r.toString(16));
|
||||
var hexG = goog.color.prependZeroIfNecessaryHelper(g.toString(16));
|
||||
var hexB = goog.color.prependZeroIfNecessaryHelper(b.toString(16));
|
||||
return '#' + hexR + hexG + hexB;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGB to hex representation.
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.rgbArrayToHex = function(rgb) {
|
||||
return goog.color.rgbToHex(rgb[0], rgb[1], rgb[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGB color space to HSL color space.
|
||||
* Modified from {@link http://en.wikipedia.org/wiki/HLS_color_space}.
|
||||
* @param {number} r Value of red, in [0, 255].
|
||||
* @param {number} g Value of green, in [0, 255].
|
||||
* @param {number} b Value of blue, in [0, 255].
|
||||
* @return {!goog.color.Hsl} hsl representation of the color.
|
||||
*/
|
||||
goog.color.rgbToHsl = function(r, g, b) {
|
||||
// First must normalize r, g, b to be between 0 and 1.
|
||||
var normR = r / 255;
|
||||
var normG = g / 255;
|
||||
var normB = b / 255;
|
||||
var max = Math.max(normR, normG, normB);
|
||||
var min = Math.min(normR, normG, normB);
|
||||
var h = 0;
|
||||
var s = 0;
|
||||
|
||||
// Luminosity is the average of the max and min rgb color intensities.
|
||||
var l = 0.5 * (max + min);
|
||||
|
||||
// The hue and saturation are dependent on which color intensity is the max.
|
||||
// If max and min are equal, the color is gray and h and s should be 0.
|
||||
if (max != min) {
|
||||
if (max == normR) {
|
||||
h = 60 * (normG - normB) / (max - min);
|
||||
} else if (max == normG) {
|
||||
h = 60 * (normB - normR) / (max - min) + 120;
|
||||
} else if (max == normB) {
|
||||
h = 60 * (normR - normG) / (max - min) + 240;
|
||||
}
|
||||
|
||||
if (0 < l && l <= 0.5) {
|
||||
s = (max - min) / (2 * l);
|
||||
} else {
|
||||
s = (max - min) / (2 - 2 * l);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the hue falls between 0 and 360.
|
||||
return [Math.round(h + 360) % 360, s, l];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from RGB color space to HSL color space.
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @return {!goog.color.Hsl} hsl representation of the color.
|
||||
*/
|
||||
goog.color.rgbArrayToHsl = function(rgb) {
|
||||
return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for hslToRgb.
|
||||
* @param {number} v1 Helper variable 1.
|
||||
* @param {number} v2 Helper variable 2.
|
||||
* @param {number} vH Helper variable 3.
|
||||
* @return {number} Appropriate RGB value, given the above.
|
||||
* @private
|
||||
*/
|
||||
goog.color.hueToRgb_ = function(v1, v2, vH) {
|
||||
if (vH < 0) {
|
||||
vH += 1;
|
||||
} else if (vH > 1) {
|
||||
vH -= 1;
|
||||
}
|
||||
if ((6 * vH) < 1) {
|
||||
return (v1 + (v2 - v1) * 6 * vH);
|
||||
} else if (2 * vH < 1) {
|
||||
return v2;
|
||||
} else if (3 * vH < 2) {
|
||||
return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6);
|
||||
}
|
||||
return v1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSL color space to RGB color space.
|
||||
* Modified from {@link http://www.easyrgb.com/math.html}
|
||||
* @param {number} h Hue, in [0, 360].
|
||||
* @param {number} s Saturation, in [0, 1].
|
||||
* @param {number} l Luminosity, in [0, 1].
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.hslToRgb = function(h, s, l) {
|
||||
var r = 0;
|
||||
var g = 0;
|
||||
var b = 0;
|
||||
var normH = h / 360; // normalize h to fall in [0, 1]
|
||||
|
||||
if (s == 0) {
|
||||
r = g = b = l * 255;
|
||||
} else {
|
||||
var temp1 = 0;
|
||||
var temp2 = 0;
|
||||
if (l < 0.5) {
|
||||
temp2 = l * (1 + s);
|
||||
} else {
|
||||
temp2 = l + s - (s * l);
|
||||
}
|
||||
temp1 = 2 * l - temp2;
|
||||
r = 255 * goog.color.hueToRgb_(temp1, temp2, normH + (1 / 3));
|
||||
g = 255 * goog.color.hueToRgb_(temp1, temp2, normH);
|
||||
b = 255 * goog.color.hueToRgb_(temp1, temp2, normH - (1 / 3));
|
||||
}
|
||||
|
||||
return [Math.round(r), Math.round(g), Math.round(b)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a color from HSL color space to RGB color space.
|
||||
* @param {goog.color.Hsl} hsl hsl representation of the color.
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.hslArrayToRgb = function(hsl) {
|
||||
return goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for isValidHexColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.validHexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid hex color. We expect strings of the format
|
||||
* #RRGGBB (ex: #1b3d5f) or #RGB (ex: #3CA == #33CCAA).
|
||||
* @param {string} str String to check.
|
||||
* @return {boolean} Whether the string is a valid hex color.
|
||||
* @private
|
||||
*/
|
||||
goog.color.isValidHexColor_ = function(str) {
|
||||
return goog.color.validHexColorRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper for isNormalizedHexColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.normalizedHexColorRe_ = /^#[0-9a-f]{6}$/;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a normalized hex color.
|
||||
* We expect strings of the format #RRGGBB (ex: #1b3d5f)
|
||||
* using only lowercase letters.
|
||||
* @param {string} str String to check.
|
||||
* @return {boolean} Whether the string is a normalized hex color.
|
||||
* @private
|
||||
*/
|
||||
goog.color.isNormalizedHexColor_ = function(str) {
|
||||
return goog.color.normalizedHexColorRe_.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for matching and capturing RGB style strings. Helper for
|
||||
* isValidRgbColor_.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.color.rgbColorRe_ =
|
||||
/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid rgb color. We expect strings of the format
|
||||
* '(r, g, b)', or 'rgb(r, g, b)', where each color component is an int in
|
||||
* [0, 255].
|
||||
* @param {string} str String to check.
|
||||
* @return {!goog.color.Rgb} the rgb representation of the color if it is
|
||||
* a valid color, or the empty array otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.color.isValidRgbColor_ = function(str) {
|
||||
// Each component is separate (rather than using a repeater) so we can
|
||||
// capture the match. Also, we explicitly set each component to be either 0,
|
||||
// or start with a non-zero, to prevent octal numbers from slipping through.
|
||||
var regExpResultArray = str.match(goog.color.rgbColorRe_);
|
||||
if (regExpResultArray) {
|
||||
var r = Number(regExpResultArray[1]);
|
||||
var g = Number(regExpResultArray[2]);
|
||||
var b = Number(regExpResultArray[3]);
|
||||
if (r >= 0 && r <= 255 &&
|
||||
g >= 0 && g <= 255 &&
|
||||
b >= 0 && b <= 255) {
|
||||
return [r, g, b];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes a hex value and prepends a zero if it's a single digit.
|
||||
* Small helper method for use by goog.color and friends.
|
||||
* @param {string} hex Hex value to prepend if single digit.
|
||||
* @return {string} hex value prepended with zero if it was single digit,
|
||||
* otherwise the same value that was passed in.
|
||||
*/
|
||||
goog.color.prependZeroIfNecessaryHelper = function(hex) {
|
||||
return hex.length == 1 ? '0' + hex : hex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes a string a prepends a '#' sign if one doesn't exist.
|
||||
* Small helper method for use by goog.color and friends.
|
||||
* @param {string} str String to check.
|
||||
* @return {string} The value passed in, prepended with a '#' if it didn't
|
||||
* already have one.
|
||||
*/
|
||||
goog.color.prependHashIfNecessaryHelper = function(str) {
|
||||
return str.charAt(0) == '#' ? str : '#' + str;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Takes an array of [r, g, b] and converts it into a string appropriate for
|
||||
* CSS styles.
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @return {string} string of the form 'rgb(r,g,b)'.
|
||||
* @private
|
||||
*/
|
||||
goog.color.rgbStyle_ = function(rgb) {
|
||||
return 'rgb(' + rgb.join(',') + ')';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an HSV triplet to an RGB array. V is brightness because b is
|
||||
* reserved for blue in RGB.
|
||||
* @param {number} h Hue value in [0, 360].
|
||||
* @param {number} s Saturation value in [0, 1].
|
||||
* @param {number} brightness brightness in [0, 255].
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.hsvToRgb = function(h, s, brightness) {
|
||||
var red = 0;
|
||||
var green = 0;
|
||||
var blue = 0;
|
||||
if (s == 0) {
|
||||
red = brightness;
|
||||
green = brightness;
|
||||
blue = brightness;
|
||||
} else {
|
||||
var sextant = Math.floor(h / 60);
|
||||
var remainder = (h / 60) - sextant;
|
||||
var val1 = brightness * (1 - s);
|
||||
var val2 = brightness * (1 - (s * remainder));
|
||||
var val3 = brightness * (1 - (s * (1 - remainder)));
|
||||
switch (sextant) {
|
||||
case 1:
|
||||
red = val2;
|
||||
green = brightness;
|
||||
blue = val1;
|
||||
break;
|
||||
case 2:
|
||||
red = val1;
|
||||
green = brightness;
|
||||
blue = val3;
|
||||
break;
|
||||
case 3:
|
||||
red = val1;
|
||||
green = val2;
|
||||
blue = brightness;
|
||||
break;
|
||||
case 4:
|
||||
red = val3;
|
||||
green = val1;
|
||||
blue = brightness;
|
||||
break;
|
||||
case 5:
|
||||
red = brightness;
|
||||
green = val1;
|
||||
blue = val2;
|
||||
break;
|
||||
case 6:
|
||||
case 0:
|
||||
red = brightness;
|
||||
green = val3;
|
||||
blue = val1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [Math.floor(red), Math.floor(green), Math.floor(blue)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from RGB values to an array of HSV values.
|
||||
* @param {number} red Red value in [0, 255].
|
||||
* @param {number} green Green value in [0, 255].
|
||||
* @param {number} blue Blue value in [0, 255].
|
||||
* @return {!goog.color.Hsv} hsv representation of the color.
|
||||
*/
|
||||
goog.color.rgbToHsv = function(red, green, blue) {
|
||||
|
||||
var max = Math.max(Math.max(red, green), blue);
|
||||
var min = Math.min(Math.min(red, green), blue);
|
||||
var hue;
|
||||
var saturation;
|
||||
var value = max;
|
||||
if (min == max) {
|
||||
hue = 0;
|
||||
saturation = 0;
|
||||
} else {
|
||||
var delta = (max - min);
|
||||
saturation = delta / max;
|
||||
|
||||
if (red == max) {
|
||||
hue = (green - blue) / delta;
|
||||
} else if (green == max) {
|
||||
hue = 2 + ((blue - red) / delta);
|
||||
} else {
|
||||
hue = 4 + ((red - green) / delta);
|
||||
}
|
||||
hue *= 60;
|
||||
if (hue < 0) {
|
||||
hue += 360;
|
||||
}
|
||||
if (hue > 360) {
|
||||
hue -= 360;
|
||||
}
|
||||
}
|
||||
|
||||
return [hue, saturation, value];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from an array of RGB values to an array of HSV values.
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @return {!goog.color.Hsv} hsv representation of the color.
|
||||
*/
|
||||
goog.color.rgbArrayToHsv = function(rgb) {
|
||||
return goog.color.rgbToHsv(rgb[0], rgb[1], rgb[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an HSV triplet to an RGB array.
|
||||
* @param {goog.color.Hsv} hsv hsv representation of the color.
|
||||
* @return {!goog.color.Rgb} rgb representation of the color.
|
||||
*/
|
||||
goog.color.hsvArrayToRgb = function(hsv) {
|
||||
return goog.color.hsvToRgb(hsv[0], hsv[1], hsv[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a hex representation of a color to HSL.
|
||||
* @param {string} hex Color to convert.
|
||||
* @return {!goog.color.Hsv} hsv representation of the color.
|
||||
*/
|
||||
goog.color.hexToHsl = function(hex) {
|
||||
var rgb = goog.color.hexToRgb(hex);
|
||||
return goog.color.rgbToHsl(rgb[0], rgb[1], rgb[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from h,s,l values to a hex string
|
||||
* @param {number} h Hue, in [0, 360].
|
||||
* @param {number} s Saturation, in [0, 1].
|
||||
* @param {number} l Luminosity, in [0, 1].
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.hslToHex = function(h, s, l) {
|
||||
return goog.color.rgbArrayToHex(goog.color.hslToRgb(h, s, l));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from an hsl array to a hex string
|
||||
* @param {goog.color.Hsl} hsl hsl representation of the color.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.hslArrayToHex = function(hsl) {
|
||||
return goog.color.rgbArrayToHex(goog.color.hslToRgb(hsl[0], hsl[1], hsl[2]));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a hex representation of a color to HSV
|
||||
* @param {string} hex Color to convert.
|
||||
* @return {!goog.color.Hsv} hsv representation of the color.
|
||||
*/
|
||||
goog.color.hexToHsv = function(hex) {
|
||||
return goog.color.rgbArrayToHsv(goog.color.hexToRgb(hex));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from h,s,v values to a hex string
|
||||
* @param {number} h Hue, in [0, 360].
|
||||
* @param {number} s Saturation, in [0, 1].
|
||||
* @param {number} v Value, in [0, 255].
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.hsvToHex = function(h, s, v) {
|
||||
return goog.color.rgbArrayToHex(goog.color.hsvToRgb(h, s, v));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts from an HSV array to a hex string
|
||||
* @param {goog.color.Hsv} hsv hsv representation of the color.
|
||||
* @return {string} hex representation of the color.
|
||||
*/
|
||||
goog.color.hsvArrayToHex = function(hsv) {
|
||||
return goog.color.hsvToHex(hsv[0], hsv[1], hsv[2]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the Euclidean distance between two color vectors on an HSL sphere.
|
||||
* A demo of the sphere can be found at:
|
||||
* http://en.wikipedia.org/wiki/HSL_color_space
|
||||
* In short, a vector for color (H, S, L) in this system can be expressed as
|
||||
* (S*L'*cos(2*PI*H), S*L'*sin(2*PI*H), L), where L' = abs(L - 0.5), and we
|
||||
* simply calculate the 1-2 distance using these coordinates
|
||||
* @param {goog.color.Hsl} hsl1 First color in hsl representation.
|
||||
* @param {goog.color.Hsl} hsl2 Second color in hsl representation.
|
||||
* @return {number} Distance between the two colors, in the range [0, 1].
|
||||
*/
|
||||
goog.color.hslDistance = function(hsl1, hsl2) {
|
||||
var sl1, sl2;
|
||||
if (hsl1[2] <= 0.5) {
|
||||
sl1 = hsl1[1] * hsl1[2];
|
||||
} else {
|
||||
sl1 = hsl1[1] * (1.0 - hsl1[2]);
|
||||
}
|
||||
|
||||
if (hsl2[2] <= 0.5) {
|
||||
sl2 = hsl2[1] * hsl2[2];
|
||||
} else {
|
||||
sl2 = hsl2[1] * (1.0 - hsl2[2]);
|
||||
}
|
||||
|
||||
var h1 = hsl1[0] / 360.0;
|
||||
var h2 = hsl2[0] / 360.0;
|
||||
var dh = (h1 - h2) * 2.0 * Math.PI;
|
||||
return (hsl1[2] - hsl2[2]) * (hsl1[2] - hsl2[2]) +
|
||||
sl1 * sl1 + sl2 * sl2 - 2 * sl1 * sl2 * Math.cos(dh);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Blend two colors together, using the specified factor to indicate the weight
|
||||
* given to the first color
|
||||
* @param {goog.color.Rgb} rgb1 First color represented in rgb.
|
||||
* @param {goog.color.Rgb} rgb2 Second color represented in rgb.
|
||||
* @param {number} factor The weight to be given to rgb1 over rgb2. Values
|
||||
* should be in the range [0, 1]. If less than 0, factor will be set to 0.
|
||||
* If greater than 1, factor will be set to 1.
|
||||
* @return {!goog.color.Rgb} Combined color represented in rgb.
|
||||
*/
|
||||
goog.color.blend = function(rgb1, rgb2, factor) {
|
||||
factor = goog.math.clamp(factor, 0, 1);
|
||||
|
||||
return [
|
||||
Math.round(factor * rgb1[0] + (1.0 - factor) * rgb2[0]),
|
||||
Math.round(factor * rgb1[1] + (1.0 - factor) * rgb2[1]),
|
||||
Math.round(factor * rgb1[2] + (1.0 - factor) * rgb2[2])
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds black to the specified color, darkening it
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @param {number} factor Number in the range [0, 1]. 0 will do nothing, while
|
||||
* 1 will return black. If less than 0, factor will be set to 0. If greater
|
||||
* than 1, factor will be set to 1.
|
||||
* @return {!goog.color.Rgb} Combined rgb color.
|
||||
*/
|
||||
goog.color.darken = function(rgb, factor) {
|
||||
var black = [0, 0, 0];
|
||||
return goog.color.blend(black, rgb, factor);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds white to the specified color, lightening it
|
||||
* @param {goog.color.Rgb} rgb rgb representation of the color.
|
||||
* @param {number} factor Number in the range [0, 1]. 0 will do nothing, while
|
||||
* 1 will return white. If less than 0, factor will be set to 0. If greater
|
||||
* than 1, factor will be set to 1.
|
||||
* @return {!goog.color.Rgb} Combined rgb color.
|
||||
*/
|
||||
goog.color.lighten = function(rgb, factor) {
|
||||
var white = [255, 255, 255];
|
||||
return goog.color.blend(white, rgb, factor);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find the "best" (highest-contrast) of the suggested colors for the prime
|
||||
* color. Uses W3C formula for judging readability and visual accessibility:
|
||||
* http://www.w3.org/TR/AERT#color-contrast
|
||||
* @param {goog.color.Rgb} prime Color represented as a rgb array.
|
||||
* @param {Array.<goog.color.Rgb>} suggestions Array of colors,
|
||||
* each representing a rgb array.
|
||||
* @return {!goog.color.Rgb} Highest-contrast color represented by an array..
|
||||
*/
|
||||
goog.color.highContrast = function(prime, suggestions) {
|
||||
var suggestionsWithDiff = [];
|
||||
for (var i = 0; i < suggestions.length; i++) {
|
||||
suggestionsWithDiff.push({
|
||||
color: suggestions[i],
|
||||
diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) +
|
||||
goog.color.colorDiff_(suggestions[i], prime)
|
||||
});
|
||||
}
|
||||
suggestionsWithDiff.sort(function(a, b) {
|
||||
return b.diff - a.diff;
|
||||
});
|
||||
return suggestionsWithDiff[0].color;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculate brightness of a color according to YIQ formula (brightness is Y).
|
||||
* More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for
|
||||
* goog.color.highContrast()
|
||||
* @param {goog.color.Rgb} rgb Color represented by a rgb array.
|
||||
* @return {number} brightness (Y).
|
||||
* @private
|
||||
*/
|
||||
goog.color.yiqBrightness_ = function(rgb) {
|
||||
return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculate difference in brightness of two colors. Helper method for
|
||||
* goog.color.highContrast()
|
||||
* @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
|
||||
* @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
|
||||
* @return {number} Brightness difference.
|
||||
* @private
|
||||
*/
|
||||
goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
|
||||
return Math.abs(goog.color.yiqBrightness_(rgb1) -
|
||||
goog.color.yiqBrightness_(rgb2));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculate color difference between two colors. Helper method for
|
||||
* goog.color.highContrast()
|
||||
* @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
|
||||
* @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
|
||||
* @return {number} Color difference.
|
||||
* @private
|
||||
*/
|
||||
goog.color.colorDiff_ = function(rgb1, rgb2) {
|
||||
return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) +
|
||||
Math.abs(rgb1[2] - rgb2[2]);
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 Names of standard colors with their associated hex values.
|
||||
*/
|
||||
|
||||
goog.provide('goog.color.names');
|
||||
|
||||
|
||||
/**
|
||||
* A map that contains a lot of colors that are recognised by various browsers.
|
||||
* This list is way larger than the minimal one dictated by W3C.
|
||||
* The keys of this map are the lowercase "readable" names of the colors, while
|
||||
* the values are the "hex" values.
|
||||
*/
|
||||
goog.color.names = {
|
||||
'aliceblue': '#f0f8ff',
|
||||
'antiquewhite': '#faebd7',
|
||||
'aqua': '#00ffff',
|
||||
'aquamarine': '#7fffd4',
|
||||
'azure': '#f0ffff',
|
||||
'beige': '#f5f5dc',
|
||||
'bisque': '#ffe4c4',
|
||||
'black': '#000000',
|
||||
'blanchedalmond': '#ffebcd',
|
||||
'blue': '#0000ff',
|
||||
'blueviolet': '#8a2be2',
|
||||
'brown': '#a52a2a',
|
||||
'burlywood': '#deb887',
|
||||
'cadetblue': '#5f9ea0',
|
||||
'chartreuse': '#7fff00',
|
||||
'chocolate': '#d2691e',
|
||||
'coral': '#ff7f50',
|
||||
'cornflowerblue': '#6495ed',
|
||||
'cornsilk': '#fff8dc',
|
||||
'crimson': '#dc143c',
|
||||
'cyan': '#00ffff',
|
||||
'darkblue': '#00008b',
|
||||
'darkcyan': '#008b8b',
|
||||
'darkgoldenrod': '#b8860b',
|
||||
'darkgray': '#a9a9a9',
|
||||
'darkgreen': '#006400',
|
||||
'darkgrey': '#a9a9a9',
|
||||
'darkkhaki': '#bdb76b',
|
||||
'darkmagenta': '#8b008b',
|
||||
'darkolivegreen': '#556b2f',
|
||||
'darkorange': '#ff8c00',
|
||||
'darkorchid': '#9932cc',
|
||||
'darkred': '#8b0000',
|
||||
'darksalmon': '#e9967a',
|
||||
'darkseagreen': '#8fbc8f',
|
||||
'darkslateblue': '#483d8b',
|
||||
'darkslategray': '#2f4f4f',
|
||||
'darkslategrey': '#2f4f4f',
|
||||
'darkturquoise': '#00ced1',
|
||||
'darkviolet': '#9400d3',
|
||||
'deeppink': '#ff1493',
|
||||
'deepskyblue': '#00bfff',
|
||||
'dimgray': '#696969',
|
||||
'dimgrey': '#696969',
|
||||
'dodgerblue': '#1e90ff',
|
||||
'firebrick': '#b22222',
|
||||
'floralwhite': '#fffaf0',
|
||||
'forestgreen': '#228b22',
|
||||
'fuchsia': '#ff00ff',
|
||||
'gainsboro': '#dcdcdc',
|
||||
'ghostwhite': '#f8f8ff',
|
||||
'gold': '#ffd700',
|
||||
'goldenrod': '#daa520',
|
||||
'gray': '#808080',
|
||||
'green': '#008000',
|
||||
'greenyellow': '#adff2f',
|
||||
'grey': '#808080',
|
||||
'honeydew': '#f0fff0',
|
||||
'hotpink': '#ff69b4',
|
||||
'indianred': '#cd5c5c',
|
||||
'indigo': '#4b0082',
|
||||
'ivory': '#fffff0',
|
||||
'khaki': '#f0e68c',
|
||||
'lavender': '#e6e6fa',
|
||||
'lavenderblush': '#fff0f5',
|
||||
'lawngreen': '#7cfc00',
|
||||
'lemonchiffon': '#fffacd',
|
||||
'lightblue': '#add8e6',
|
||||
'lightcoral': '#f08080',
|
||||
'lightcyan': '#e0ffff',
|
||||
'lightgoldenrodyellow': '#fafad2',
|
||||
'lightgray': '#d3d3d3',
|
||||
'lightgreen': '#90ee90',
|
||||
'lightgrey': '#d3d3d3',
|
||||
'lightpink': '#ffb6c1',
|
||||
'lightsalmon': '#ffa07a',
|
||||
'lightseagreen': '#20b2aa',
|
||||
'lightskyblue': '#87cefa',
|
||||
'lightslategray': '#778899',
|
||||
'lightslategrey': '#778899',
|
||||
'lightsteelblue': '#b0c4de',
|
||||
'lightyellow': '#ffffe0',
|
||||
'lime': '#00ff00',
|
||||
'limegreen': '#32cd32',
|
||||
'linen': '#faf0e6',
|
||||
'magenta': '#ff00ff',
|
||||
'maroon': '#800000',
|
||||
'mediumaquamarine': '#66cdaa',
|
||||
'mediumblue': '#0000cd',
|
||||
'mediumorchid': '#ba55d3',
|
||||
'mediumpurple': '#9370d8',
|
||||
'mediumseagreen': '#3cb371',
|
||||
'mediumslateblue': '#7b68ee',
|
||||
'mediumspringgreen': '#00fa9a',
|
||||
'mediumturquoise': '#48d1cc',
|
||||
'mediumvioletred': '#c71585',
|
||||
'midnightblue': '#191970',
|
||||
'mintcream': '#f5fffa',
|
||||
'mistyrose': '#ffe4e1',
|
||||
'moccasin': '#ffe4b5',
|
||||
'navajowhite': '#ffdead',
|
||||
'navy': '#000080',
|
||||
'oldlace': '#fdf5e6',
|
||||
'olive': '#808000',
|
||||
'olivedrab': '#6b8e23',
|
||||
'orange': '#ffa500',
|
||||
'orangered': '#ff4500',
|
||||
'orchid': '#da70d6',
|
||||
'palegoldenrod': '#eee8aa',
|
||||
'palegreen': '#98fb98',
|
||||
'paleturquoise': '#afeeee',
|
||||
'palevioletred': '#d87093',
|
||||
'papayawhip': '#ffefd5',
|
||||
'peachpuff': '#ffdab9',
|
||||
'peru': '#cd853f',
|
||||
'pink': '#ffc0cb',
|
||||
'plum': '#dda0dd',
|
||||
'powderblue': '#b0e0e6',
|
||||
'purple': '#800080',
|
||||
'red': '#ff0000',
|
||||
'rosybrown': '#bc8f8f',
|
||||
'royalblue': '#4169e1',
|
||||
'saddlebrown': '#8b4513',
|
||||
'salmon': '#fa8072',
|
||||
'sandybrown': '#f4a460',
|
||||
'seagreen': '#2e8b57',
|
||||
'seashell': '#fff5ee',
|
||||
'sienna': '#a0522d',
|
||||
'silver': '#c0c0c0',
|
||||
'skyblue': '#87ceeb',
|
||||
'slateblue': '#6a5acd',
|
||||
'slategray': '#708090',
|
||||
'slategrey': '#708090',
|
||||
'snow': '#fffafa',
|
||||
'springgreen': '#00ff7f',
|
||||
'steelblue': '#4682b4',
|
||||
'tan': '#d2b48c',
|
||||
'teal': '#008080',
|
||||
'thistle': '#d8bfd8',
|
||||
'tomato': '#ff6347',
|
||||
'turquoise': '#40e0d0',
|
||||
'violet': '#ee82ee',
|
||||
'wheat': '#f5deb3',
|
||||
'white': '#ffffff',
|
||||
'whitesmoke': '#f5f5f5',
|
||||
'yellow': '#ffff00',
|
||||
'yellowgreen': '#9acd32'
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 ARC4 streamcipher implementation. A description of the
|
||||
* algorithm can be found at:
|
||||
* http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt.
|
||||
*
|
||||
* Usage:
|
||||
* <code>
|
||||
* var arc4 = new goog.crypt.Arc4();
|
||||
* arc4.setKey(key);
|
||||
* arc4.discard(1536);
|
||||
* arc4.crypt(bytes);
|
||||
* </code>
|
||||
*
|
||||
* Note: For converting between strings and byte arrays, goog.crypt.base64 may
|
||||
* be useful.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.Arc4');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ARC4 streamcipher implementation.
|
||||
* @constructor
|
||||
*/
|
||||
goog.crypt.Arc4 = function() {
|
||||
/**
|
||||
* A permutation of all 256 possible bytes.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.state_ = [];
|
||||
|
||||
/**
|
||||
* 8 bit index pointer into this.state_.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.index1_ = 0;
|
||||
|
||||
/**
|
||||
* 8 bit index pointer into this.state_.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.index2_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the cipher for use with new key.
|
||||
* @param {Array.<number>} key A byte array containing the key.
|
||||
* @param {number=} opt_length Indicates # of bytes to take from the key.
|
||||
*/
|
||||
goog.crypt.Arc4.prototype.setKey = function(key, opt_length) {
|
||||
goog.asserts.assertArray(key, 'Key parameter must be a byte array');
|
||||
|
||||
if (!opt_length) {
|
||||
opt_length = key.length;
|
||||
}
|
||||
|
||||
var state = this.state_;
|
||||
|
||||
for (var i = 0; i < 256; ++i) {
|
||||
state[i] = i;
|
||||
}
|
||||
|
||||
var j = 0;
|
||||
for (var i = 0; i < 256; ++i) {
|
||||
j = (j + state[i] + key[i % opt_length]) & 255;
|
||||
|
||||
var tmp = state[i];
|
||||
state[i] = state[j];
|
||||
state[j] = tmp;
|
||||
}
|
||||
|
||||
this.index1_ = 0;
|
||||
this.index2_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Discards n bytes of the keystream.
|
||||
* These days 1536 is considered a decent amount to drop to get the key state
|
||||
* warmed-up enough for secure usage. This is not done in the constructor to
|
||||
* preserve efficiency for use cases that do not need this.
|
||||
* NOTE: Discard is identical to crypt without actually xoring any data. It's
|
||||
* unfortunate to have this code duplicated, but this was done for performance
|
||||
* reasons. Alternatives which were attempted:
|
||||
* 1. Create a temp array of the correct length and pass it to crypt. This
|
||||
* works but needlessly allocates an array. But more importantly this
|
||||
* requires choosing an array type (Array or Uint8Array) in discard, and
|
||||
* choosing a different type than will be passed to crypt by the client
|
||||
* code hurts the javascript engines ability to optimize crypt (7x hit in
|
||||
* v8).
|
||||
* 2. Make data option in crypt so discard can pass null, this has a huge
|
||||
* perf hit for crypt.
|
||||
* @param {number} length Number of bytes to disregard from the stream.
|
||||
*/
|
||||
goog.crypt.Arc4.prototype.discard = function(length) {
|
||||
var i = this.index1_;
|
||||
var j = this.index2_;
|
||||
var state = this.state_;
|
||||
|
||||
for (var n = 0; n < length; ++n) {
|
||||
i = (i + 1) & 255;
|
||||
j = (j + state[i]) & 255;
|
||||
|
||||
var tmp = state[i];
|
||||
state[i] = state[j];
|
||||
state[j] = tmp;
|
||||
}
|
||||
|
||||
this.index1_ = i;
|
||||
this.index2_ = j;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* En- or decrypt (same operation for streamciphers like ARC4)
|
||||
* @param {Array.<number>|Uint8Array} data The data to be xor-ed in place.
|
||||
* @param {number=} opt_length The number of bytes to crypt.
|
||||
*/
|
||||
goog.crypt.Arc4.prototype.crypt = function(data, opt_length) {
|
||||
if (!opt_length) {
|
||||
opt_length = data.length;
|
||||
}
|
||||
var i = this.index1_;
|
||||
var j = this.index2_;
|
||||
var state = this.state_;
|
||||
|
||||
for (var n = 0; n < opt_length; ++n) {
|
||||
i = (i + 1) & 255;
|
||||
j = (j + state[i]) & 255;
|
||||
|
||||
var tmp = state[i];
|
||||
state[i] = state[j];
|
||||
state[j] = tmp;
|
||||
|
||||
data[n] ^= state[(state[i] + state[j]) & 255];
|
||||
}
|
||||
|
||||
this.index1_ = i;
|
||||
this.index2_ = j;
|
||||
};
|
||||
@@ -0,0 +1,272 @@
|
||||
// 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)
|
||||
* @author fschneider@google.com (Fritz Schneider)
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.base64');
|
||||
goog.require('goog.crypt');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
// Static lookup maps, lazily populated by init_()
|
||||
|
||||
|
||||
/**
|
||||
* Maps bytes to characters.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.base64.byteToCharMap_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Maps characters to bytes.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.base64.charToByteMap_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Maps bytes to websafe characters.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.base64.byteToCharMapWebSafe_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Maps websafe characters to bytes.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.base64.charToByteMapWebSafe_ = 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 + '-_.';
|
||||
|
||||
|
||||
/**
|
||||
* Whether this browser supports the atob and btoa functions. This extension
|
||||
* started at Mozilla but is now implemented by many browsers. We use the
|
||||
* ASSUME_* variables to avoid pulling in the full useragent detection library
|
||||
* but still allowing the standard per-browser compilations.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.crypt.base64.HAS_NATIVE_SUPPORT = goog.userAgent.GECKO ||
|
||||
goog.userAgent.WEBKIT ||
|
||||
goog.userAgent.OPERA ||
|
||||
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 Boolean indicating we should use the
|
||||
* alternative alphabet.
|
||||
* @return {string} The base64 encoded string.
|
||||
*/
|
||||
goog.crypt.base64.encodeByteArray = function(input, opt_webSafe) {
|
||||
if (!goog.isArrayLike(input)) {
|
||||
throw Error('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 If true, we should use the
|
||||
* alternative alphabet.
|
||||
* @return {string} The base64 encoded string.
|
||||
*/
|
||||
goog.crypt.base64.encodeString = function(input, opt_webSafe) {
|
||||
// Shortcut for Mozilla browsers that implement
|
||||
// a native base64 encoder in the form of "btoa/atob"
|
||||
if (goog.crypt.base64.HAS_NATIVE_SUPPORT && !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 to decode.
|
||||
* @param {boolean=} opt_webSafe True if we should use the
|
||||
* alternative alphabet.
|
||||
* @return {string} string representing the decoded value.
|
||||
*/
|
||||
goog.crypt.base64.decodeString = function(input, opt_webSafe) {
|
||||
// Shortcut for Mozilla browsers that implement
|
||||
// a native base64 encoder in the form of "btoa/atob"
|
||||
if (goog.crypt.base64.HAS_NATIVE_SUPPORT && !opt_webSafe) {
|
||||
return goog.global.atob(input);
|
||||
}
|
||||
return goog.crypt.byteArrayToString(
|
||||
goog.crypt.base64.decodeStringToByteArray(input, opt_webSafe));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Base64-decode a string.
|
||||
*
|
||||
* @param {string} input to decode (length not required to be a multiple of 4).
|
||||
* @param {boolean=} opt_webSafe True if we should use the
|
||||
* alternative alphabet.
|
||||
* @return {Array} bytes representing the decoded value.
|
||||
*/
|
||||
goog.crypt.base64.decodeStringToByteArray = function(input, opt_webSafe) {
|
||||
goog.crypt.base64.init_();
|
||||
|
||||
var charToByteMap = opt_webSafe ?
|
||||
goog.crypt.base64.charToByteMapWebSafe_ :
|
||||
goog.crypt.base64.charToByteMap_;
|
||||
|
||||
var output = [];
|
||||
|
||||
for (var i = 0; i < input.length; ) {
|
||||
var byte1 = charToByteMap[input.charAt(i++)];
|
||||
|
||||
var haveByte2 = i < input.length;
|
||||
var byte2 = haveByte2 ? charToByteMap[input.charAt(i)] : 0;
|
||||
++i;
|
||||
|
||||
var haveByte3 = i < input.length;
|
||||
var byte3 = haveByte3 ? charToByteMap[input.charAt(i)] : 0;
|
||||
++i;
|
||||
|
||||
var haveByte4 = i < input.length;
|
||||
var byte4 = haveByte4 ? charToByteMap[input.charAt(i)] : 0;
|
||||
++i;
|
||||
|
||||
if (byte1 == null || byte2 == null ||
|
||||
byte3 == null || byte4 == null) {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
var outByte1 = (byte1 << 2) | (byte2 >> 4);
|
||||
output.push(outByte1);
|
||||
|
||||
if (byte3 != 64) {
|
||||
var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2);
|
||||
output.push(outByte2);
|
||||
|
||||
if (byte4 != 64) {
|
||||
var outByte3 = ((byte3 << 6) & 0xC0) | byte4;
|
||||
output.push(outByte3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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_ = {};
|
||||
goog.crypt.base64.charToByteMapWebSafe_ = {};
|
||||
|
||||
// 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);
|
||||
goog.crypt.base64.charToByteMapWebSafe_[
|
||||
goog.crypt.base64.byteToCharMapWebSafe_[i]] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,242 @@
|
||||
// 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 Numeric base conversion library. Works for arbitrary bases and
|
||||
* arbitrary length numbers.
|
||||
*
|
||||
* For base-64 conversion use base64.js because it is optimized for the specific
|
||||
* conversion to base-64 while this module is generic. Base-64 is defined here
|
||||
* mostly for demonstration purpose.
|
||||
*
|
||||
* TODO: Make base64 and baseN classes that have common interface. (Perhaps...)
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.baseN');
|
||||
|
||||
|
||||
/**
|
||||
* Base-2, i.e. '01'.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_BINARY = '01';
|
||||
|
||||
|
||||
/**
|
||||
* Base-8, i.e. '01234567'.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_OCTAL = '01234567';
|
||||
|
||||
|
||||
/**
|
||||
* Base-10, i.e. '0123456789'.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_DECIMAL = '0123456789';
|
||||
|
||||
|
||||
/**
|
||||
* Base-16 using lower case, i.e. '0123456789abcdef'.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_LOWERCASE_HEXADECIMAL = '0123456789abcdef';
|
||||
|
||||
|
||||
/**
|
||||
* Base-16 using upper case, i.e. '0123456789ABCDEF'.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_UPPERCASE_HEXADECIMAL = '0123456789ABCDEF';
|
||||
|
||||
|
||||
/**
|
||||
* The more-known version of the BASE-64 encoding. Uses + and / characters.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_64 =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
|
||||
|
||||
/**
|
||||
* URL-safe version of the BASE-64 encoding.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.crypt.baseN.BASE_64_URL_SAFE =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
|
||||
|
||||
/**
|
||||
* Converts a number from one numeric base to another.
|
||||
*
|
||||
* The bases are represented as strings, which list allowed digits. Each digit
|
||||
* should be unique. The bases can either be user defined, or any of
|
||||
* goog.crypt.baseN.BASE_xxx.
|
||||
*
|
||||
* The number is in human-readable format, most significant digit first, and is
|
||||
* a non-negative integer. Base designators such as $, 0x, d, b or h (at end)
|
||||
* will be interpreted as digits, so avoid them. Leading zeros will be trimmed.
|
||||
*
|
||||
* Note: for huge bases the result may be inaccurate because of overflowing
|
||||
* 64-bit doubles used by JavaScript for integer calculus. This may happen
|
||||
* if the product of the number of digits in the input and output bases comes
|
||||
* close to 10^16, which is VERY unlikely (100M digits in each base), but
|
||||
* may be possible in the future unicode world. (Unicode 3.2 has less than 100K
|
||||
* characters. However, it reserves some more, close to 1M.)
|
||||
*
|
||||
* @param {string} number The number to convert.
|
||||
* @param {string} inputBase The numeric base the number is in (all digits).
|
||||
* @param {string} outputBase Requested numeric base.
|
||||
* @return {string} The converted number.
|
||||
*/
|
||||
goog.crypt.baseN.recodeString = function(number, inputBase, outputBase) {
|
||||
if (outputBase == '') {
|
||||
throw Error('Empty output base');
|
||||
}
|
||||
|
||||
// Check if number is 0 (special case when we don't want to return '').
|
||||
var isZero = true;
|
||||
for (var i = 0, n = number.length; i < n; i++) {
|
||||
if (number.charAt(i) != inputBase.charAt(0)) {
|
||||
isZero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isZero) {
|
||||
return outputBase.charAt(0);
|
||||
}
|
||||
|
||||
var numberDigits = goog.crypt.baseN.stringToArray_(number, inputBase);
|
||||
|
||||
var inputBaseSize = inputBase.length;
|
||||
var outputBaseSize = outputBase.length;
|
||||
|
||||
// result = 0.
|
||||
var result = [];
|
||||
|
||||
// For all digits of number, starting with the most significant ...
|
||||
for (var i = numberDigits.length - 1; i >= 0; i--) {
|
||||
|
||||
// result *= number.base.
|
||||
var carry = 0;
|
||||
for (var j = 0, n = result.length; j < n; j++) {
|
||||
var digit = result[j];
|
||||
// This may overflow for huge bases. See function comment.
|
||||
digit = digit * inputBaseSize + carry;
|
||||
if (digit >= outputBaseSize) {
|
||||
var remainder = digit % outputBaseSize;
|
||||
carry = (digit - remainder) / outputBaseSize;
|
||||
digit = remainder;
|
||||
} else {
|
||||
carry = 0;
|
||||
}
|
||||
result[j] = digit;
|
||||
}
|
||||
while (carry) {
|
||||
var remainder = carry % outputBaseSize;
|
||||
result.push(remainder);
|
||||
carry = (carry - remainder) / outputBaseSize;
|
||||
}
|
||||
|
||||
// result += number[i].
|
||||
carry = numberDigits[i];
|
||||
var j = 0;
|
||||
while (carry) {
|
||||
if (j >= result.length) {
|
||||
// Extend result with a leading zero which will be overwritten below.
|
||||
result.push(0);
|
||||
}
|
||||
var digit = result[j];
|
||||
digit += carry;
|
||||
if (digit >= outputBaseSize) {
|
||||
var remainder = digit % outputBaseSize;
|
||||
carry = (digit - remainder) / outputBaseSize;
|
||||
digit = remainder;
|
||||
} else {
|
||||
carry = 0;
|
||||
}
|
||||
result[j] = digit;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
return goog.crypt.baseN.arrayToString_(result, outputBase);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a string representation of a number to an array of digit values.
|
||||
*
|
||||
* More precisely, the digit values are indices into the number base, which
|
||||
* is represented as a string, which can either be user defined or one of the
|
||||
* BASE_xxx constants.
|
||||
*
|
||||
* Throws an Error if the number contains a digit not found in the base.
|
||||
*
|
||||
* @param {string} number The string to convert, most significant digit first.
|
||||
* @param {string} base Digits in the base.
|
||||
* @return {Array.<number>} Array of digit values, least significant digit
|
||||
* first.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.baseN.stringToArray_ = function(number, base) {
|
||||
var index = {};
|
||||
for (var i = 0, n = base.length; i < n; i++) {
|
||||
index[base.charAt(i)] = i;
|
||||
}
|
||||
var result = [];
|
||||
for (var i = number.length - 1; i >= 0; i--) {
|
||||
var character = number.charAt(i);
|
||||
var digit = index[character];
|
||||
if (typeof digit == 'undefined') {
|
||||
throw Error('Number ' + number +
|
||||
' contains a character not found in base ' +
|
||||
base + ', which is ' + character);
|
||||
}
|
||||
result.push(digit);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an array representation of a number to a string.
|
||||
*
|
||||
* More precisely, the elements of the input array are indices into the base,
|
||||
* which is represented as a string, which can either be user defined or one of
|
||||
* the BASE_xxx constants.
|
||||
*
|
||||
* Throws an Error if the number contains a digit which is outside the range
|
||||
* 0 ... base.length - 1.
|
||||
*
|
||||
* @param {Array.<number>} number Array of digit values, least significant
|
||||
* first.
|
||||
* @param {string} base Digits in the base.
|
||||
* @return {string} Number as a string, most significant digit first.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.baseN.arrayToString_ = function(number, base) {
|
||||
var n = number.length;
|
||||
var chars = [];
|
||||
var baseSize = base.length;
|
||||
for (var i = n - 1; i >= 0; i--) {
|
||||
var digit = number[i];
|
||||
if (digit >= baseSize || digit < 0) {
|
||||
throw Error('Number ' + number + ' contains an invalid digit: ' + digit);
|
||||
}
|
||||
chars.push(base.charAt(digit));
|
||||
}
|
||||
return chars.join('');
|
||||
};
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 Asynchronous hash computer for the Blob interface.
|
||||
*
|
||||
* The Blob interface, part of the HTML5 File API, is supported on Chrome 7+,
|
||||
* Firefox 4.0 and Opera 11. No Blob interface implementation is expected on
|
||||
* Internet Explorer 10. Chrome 11, Firefox 5.0 and the subsequent release of
|
||||
* Opera are supposed to use vendor prefixes due to evolving API, see
|
||||
* http://dev.w3.org/2006/webapi/FileAPI/ for details.
|
||||
*
|
||||
* This implementation currently uses upcoming Chrome and Firefox prefixes,
|
||||
* plus the original Blob.slice specification, as implemented on Chrome 10
|
||||
* and Firefox 4.0.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.BlobHasher');
|
||||
goog.provide('goog.crypt.BlobHasher.EventType');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.crypt');
|
||||
goog.require('goog.crypt.Hash');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.fs');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Construct the hash computer.
|
||||
*
|
||||
* @param {!goog.crypt.Hash} hashFn The hash function to use.
|
||||
* @param {number=} opt_blockSize Processing block size.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.crypt.BlobHasher = function(hashFn, opt_blockSize) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The actual hash function.
|
||||
* @type {!goog.crypt.Hash}
|
||||
* @private
|
||||
*/
|
||||
this.hashFn_ = hashFn;
|
||||
|
||||
/**
|
||||
* The blob being processed.
|
||||
* @type {Blob}
|
||||
* @private
|
||||
*/
|
||||
this.blob_ = null;
|
||||
|
||||
/**
|
||||
* Computed hash value.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.hashVal_ = null;
|
||||
|
||||
/**
|
||||
* Number of bytes already processed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.bytesProcessed_ = 0;
|
||||
|
||||
/**
|
||||
* Processing block size.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.blockSize_ = opt_blockSize || 5000000;
|
||||
|
||||
/**
|
||||
* File reader object.
|
||||
* @type {FileReader}
|
||||
* @private
|
||||
*/
|
||||
this.fileReader_ = null;
|
||||
|
||||
/**
|
||||
* The logger used by this object.
|
||||
* @type {!goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
this.logger_ = goog.debug.Logger.getLogger('goog.crypt.BlobHasher');
|
||||
};
|
||||
goog.inherits(goog.crypt.BlobHasher, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Event names for hash computation events
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.crypt.BlobHasher.EventType = {
|
||||
STARTED: 'started',
|
||||
PROGRESS: 'progress',
|
||||
COMPLETE: 'complete',
|
||||
ABORT: 'abort',
|
||||
ERROR: 'error'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Start the hash computation.
|
||||
* @param {!Blob} blob The blob of data to compute the hash for.
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.hash = function(blob) {
|
||||
this.abort();
|
||||
this.hashFn_.reset();
|
||||
this.blob_ = blob;
|
||||
this.hashVal_ = null;
|
||||
this.bytesProcessed_ = 0;
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.STARTED);
|
||||
|
||||
this.processNextBlock_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Abort hash computation.
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.abort = function() {
|
||||
if (this.fileReader_ &&
|
||||
this.fileReader_.readyState != this.fileReader_.DONE) {
|
||||
this.fileReader_.abort();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} Number of bytes processed so far.
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.getBytesProcessed = function() {
|
||||
return this.bytesProcessed_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array.<number>} The computed hash value or null if not ready.
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.getHash = function() {
|
||||
return this.hashVal_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function setting up the processing for the next block, or finalizing
|
||||
* the computation if all blocks were processed.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.processNextBlock_ = function() {
|
||||
goog.asserts.assert(this.blob_, 'The blob has disappeared during processing');
|
||||
if (this.bytesProcessed_ < this.blob_.size) {
|
||||
// We have to reset the FileReader every time, otherwise it fails on
|
||||
// Chrome, including the latest Chrome 12 beta.
|
||||
// http://code.google.com/p/chromium/issues/detail?id=82346
|
||||
this.fileReader_ = new FileReader();
|
||||
this.fileReader_.onload = goog.bind(this.onLoad_, this);
|
||||
this.fileReader_.onabort = goog.bind(this.dispatchEvent, this,
|
||||
goog.crypt.BlobHasher.EventType.ABORT);
|
||||
this.fileReader_.onerror = goog.bind(this.dispatchEvent, this,
|
||||
goog.crypt.BlobHasher.EventType.ERROR);
|
||||
|
||||
var size = Math.min(this.blob_.size - this.bytesProcessed_,
|
||||
this.blockSize_);
|
||||
var chunk = goog.fs.sliceBlob(this.blob_, this.bytesProcessed_,
|
||||
this.bytesProcessed_ + size);
|
||||
if (!chunk || chunk.size != size) {
|
||||
this.logger_.severe('Failed slicing the blob');
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fileReader_.readAsArrayBuffer) {
|
||||
this.fileReader_.readAsArrayBuffer(chunk);
|
||||
} else if (this.fileReader_.readAsBinaryString) {
|
||||
this.fileReader_.readAsBinaryString(chunk);
|
||||
} else {
|
||||
this.logger_.severe('Failed calling the chunk reader');
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);
|
||||
}
|
||||
} else {
|
||||
this.hashVal_ = this.hashFn_.digest();
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.COMPLETE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle processing block loaded.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.BlobHasher.prototype.onLoad_ = function() {
|
||||
this.logger_.info('Successfully loaded a chunk');
|
||||
|
||||
var array = null;
|
||||
if (this.fileReader_.result instanceof Array ||
|
||||
goog.isString(this.fileReader_.result)) {
|
||||
array = this.fileReader_.result;
|
||||
} else if (goog.global['ArrayBuffer'] && goog.global['Uint8Array'] &&
|
||||
this.fileReader_.result instanceof ArrayBuffer) {
|
||||
array = new Uint8Array(this.fileReader_.result);
|
||||
}
|
||||
if (!array) {
|
||||
this.logger_.severe('Failed reading the chunk');
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.ERROR);
|
||||
return;
|
||||
}
|
||||
this.hashFn_.update(array);
|
||||
this.bytesProcessed_ += array.length;
|
||||
this.dispatchEvent(goog.crypt.BlobHasher.EventType.PROGRESS);
|
||||
|
||||
this.processNextBlock_();
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
// 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');
|
||||
|
||||
|
||||
/**
|
||||
* Turns a string into an array of bytes; a "byte" being a JS number in the
|
||||
* range 0-255.
|
||||
* @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);
|
||||
while (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 {Array} array Array of numbers representing characters.
|
||||
* @return {string} Stringification of the array.
|
||||
*/
|
||||
goog.crypt.byteArrayToString = function(array) {
|
||||
return String.fromCharCode.apply(null, array);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Turns an array of numbers into the hex string given by the concatenation of
|
||||
* the hex values to which the numbers correspond.
|
||||
* @param {Array} 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 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
|
||||
str = str.replace(/\r\n/g, '\n');
|
||||
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 {
|
||||
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 {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 {
|
||||
var c2 = bytes[pos++];
|
||||
var c3 = bytes[pos++];
|
||||
out[c++] = String.fromCharCode(
|
||||
(c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
|
||||
}
|
||||
}
|
||||
return out.join('');
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 Abstract cryptographic hash interface.
|
||||
*
|
||||
* See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.Hash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a cryptographic hash instance.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.crypt.Hash = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the internal accumulator.
|
||||
*/
|
||||
goog.crypt.Hash.prototype.reset = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a byte array (array with values in [0-255] range) or a string (might
|
||||
* only contain 8-bit, i.e., Latin1 characters) to the internal accumulator.
|
||||
*
|
||||
* @param {Array.<number>|Uint8Array|string} bytes Data used for the update.
|
||||
* @param {number=} opt_length Number of bytes to use.
|
||||
*/
|
||||
goog.crypt.Hash.prototype.update = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array.<number>} The finalized hash computed
|
||||
* from the internal accumulator.
|
||||
*/
|
||||
goog.crypt.Hash.prototype.digest = goog.abstractMethod;
|
||||
@@ -0,0 +1,184 @@
|
||||
// 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 Implementation of 32-bit hashing functions.
|
||||
*
|
||||
* This is a direct port from the Google Java Hash class
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.hash32');
|
||||
|
||||
goog.require('goog.crypt');
|
||||
|
||||
|
||||
/**
|
||||
* Default seed used during hashing, digits of pie.
|
||||
* See SEED32 in http://go/base.hash.java
|
||||
* @type {number}
|
||||
*/
|
||||
goog.crypt.hash32.SEED32 = 314159265;
|
||||
|
||||
|
||||
/**
|
||||
* Arbitrary constant used during hashing.
|
||||
* See CONSTANT32 in http://go/base.hash.java
|
||||
* @type {number}
|
||||
*/
|
||||
goog.crypt.hash32.CONSTANT32 = -1640531527;
|
||||
|
||||
|
||||
/**
|
||||
* Hashes a string to a 32-bit value.
|
||||
* @param {string} str String to hash.
|
||||
* @return {number} 32-bit hash.
|
||||
*/
|
||||
goog.crypt.hash32.encodeString = function(str) {
|
||||
return goog.crypt.hash32.encodeByteArray(goog.crypt.stringToByteArray(str));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hashes a string to a 32-bit value, converting the string to UTF-8 before
|
||||
* doing the encoding.
|
||||
* @param {string} str String to hash.
|
||||
* @return {number} 32-bit hash.
|
||||
*/
|
||||
goog.crypt.hash32.encodeStringUtf8 = function(str) {
|
||||
return goog.crypt.hash32.encodeByteArray(
|
||||
goog.crypt.stringToUtf8ByteArray(str));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hashes an integer to a 32-bit value.
|
||||
* @param {number} value Number to hash.
|
||||
* @return {number} 32-bit hash.
|
||||
*/
|
||||
goog.crypt.hash32.encodeInteger = function(value) {
|
||||
// TODO(user): Does this make sense in JavaScript with doubles? Should we
|
||||
// force the value to be in the correct range?
|
||||
return goog.crypt.hash32.mix32_({
|
||||
a: value,
|
||||
b: goog.crypt.hash32.CONSTANT32,
|
||||
c: goog.crypt.hash32.SEED32
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hashes a "byte" array to a 32-bit value using the supplied seed.
|
||||
* @param {Array.<number>} bytes Array of bytes.
|
||||
* @param {number=} opt_offset The starting position to use for hash
|
||||
* computation.
|
||||
* @param {number=} opt_length Number of bytes that are used for hashing.
|
||||
* @param {number=} opt_seed The seed.
|
||||
* @return {number} 32-bit hash.
|
||||
*/
|
||||
goog.crypt.hash32.encodeByteArray = function(
|
||||
bytes, opt_offset, opt_length, opt_seed) {
|
||||
var offset = opt_offset || 0;
|
||||
var length = opt_length || bytes.length;
|
||||
var seed = opt_seed || goog.crypt.hash32.SEED32;
|
||||
|
||||
var mix = {
|
||||
a: goog.crypt.hash32.CONSTANT32,
|
||||
b: goog.crypt.hash32.CONSTANT32,
|
||||
c: seed
|
||||
};
|
||||
|
||||
var keylen;
|
||||
for (keylen = length; keylen >= 12; keylen -= 12, offset += 12) {
|
||||
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
|
||||
mix.b += goog.crypt.hash32.wordAt_(bytes, offset + 4);
|
||||
mix.c += goog.crypt.hash32.wordAt_(bytes, offset + 8);
|
||||
goog.crypt.hash32.mix32_(mix);
|
||||
}
|
||||
// Hash any remaining bytes
|
||||
mix.c += length;
|
||||
switch (keylen) { // deal with rest. Some cases fall through
|
||||
case 11: mix.c += (bytes[offset + 10]) << 24;
|
||||
case 10: mix.c += (bytes[offset + 9] & 0xff) << 16;
|
||||
case 9 : mix.c += (bytes[offset + 8] & 0xff) << 8;
|
||||
// the first byte of c is reserved for the length
|
||||
case 8 :
|
||||
mix.b += goog.crypt.hash32.wordAt_(bytes, offset + 4);
|
||||
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
|
||||
break;
|
||||
case 7 : mix.b += (bytes[offset + 6] & 0xff) << 16;
|
||||
case 6 : mix.b += (bytes[offset + 5] & 0xff) << 8;
|
||||
case 5 : mix.b += (bytes[offset + 4] & 0xff);
|
||||
case 4 :
|
||||
mix.a += goog.crypt.hash32.wordAt_(bytes, offset);
|
||||
break;
|
||||
case 3 : mix.a += (bytes[offset + 2] & 0xff) << 16;
|
||||
case 2 : mix.a += (bytes[offset + 1] & 0xff) << 8;
|
||||
case 1 : mix.a += (bytes[offset + 0] & 0xff);
|
||||
// case 0 : nothing left to add
|
||||
}
|
||||
return goog.crypt.hash32.mix32_(mix);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Performs an inplace mix of an object with the integer properties (a, b, c)
|
||||
* and returns the final value of c.
|
||||
* @param {Object} mix Object with properties, a, b, and c.
|
||||
* @return {number} The end c-value for the mixing.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.hash32.mix32_ = function(mix) {
|
||||
var a = mix.a, b = mix.b, c = mix.c;
|
||||
a -= b; a -= c; a ^= c >>> 13;
|
||||
b -= c; b -= a; b ^= a << 8;
|
||||
c -= a; c -= b; c ^= b >>> 13;
|
||||
a -= b; a -= c; a ^= c >>> 12;
|
||||
b -= c; b -= a; b ^= a << 16;
|
||||
c -= a; c -= b; c ^= b >>> 5;
|
||||
a -= b; a -= c; a ^= c >>> 3;
|
||||
b -= c; b -= a; b ^= a << 10;
|
||||
c -= a; c -= b; c ^= b >>> 15;
|
||||
mix.a = a; mix.b = b; mix.c = c;
|
||||
return c;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the word at a given offset. Treating an array of bytes a word at a
|
||||
* time is far more efficient than byte-by-byte.
|
||||
* @param {Array.<number>} bytes Array of bytes.
|
||||
* @param {number} offset Offset in the byte array.
|
||||
* @return {number} Integer value for the word.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.hash32.wordAt_ = function(bytes, offset) {
|
||||
var a = goog.crypt.hash32.toSigned_(bytes[offset + 0]);
|
||||
var b = goog.crypt.hash32.toSigned_(bytes[offset + 1]);
|
||||
var c = goog.crypt.hash32.toSigned_(bytes[offset + 2]);
|
||||
var d = goog.crypt.hash32.toSigned_(bytes[offset + 3]);
|
||||
return a + (b << 8) + (c << 16) + (d << 24);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an unsigned "byte" to signed, that is, convert a value in the range
|
||||
* (0, 2^8-1) to (-2^7, 2^7-1) in order to be compatible with Java's byte type.
|
||||
* @param {number} n Unsigned "byte" value.
|
||||
* @return {number} Signed "byte" value.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.hash32.toSigned_ = function(n) {
|
||||
return n > 127 ? n - 256 : n;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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 Unit tests for the abstract cryptographic hash interface.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.hash_test');
|
||||
|
||||
goog.require('goog.testing.asserts');
|
||||
goog.setTestOnly('hash_test');
|
||||
|
||||
|
||||
goog.crypt.hash_test.runBasicTests = function(hash) {
|
||||
// Compute some hash.
|
||||
hash.update([97, 158]);
|
||||
var golden = hash.digest();
|
||||
|
||||
// Recompute the hash.
|
||||
hash.reset();
|
||||
hash.update([97, 158]);
|
||||
assertArrayEquals('The reset did not produce the initial state',
|
||||
golden, hash.digest());
|
||||
|
||||
// Check for a trivial collision.
|
||||
hash.reset();
|
||||
hash.update([158, 97]);
|
||||
assertTrue('Swapping bytes resulted in a hash collision',
|
||||
!!goog.testing.asserts.findDifferences(golden, hash.digest()));
|
||||
|
||||
// Compute in parts.
|
||||
hash.reset();
|
||||
hash.update([97]);
|
||||
hash.update([158]);
|
||||
assertArrayEquals('Partial updates resulted in a different hash',
|
||||
golden, hash.digest());
|
||||
|
||||
// Test update with specified length.
|
||||
hash.reset();
|
||||
hash.update([97, 158], 0);
|
||||
hash.update([97, 158, 32], 2);
|
||||
assertArrayEquals('Updating with an explicit buffer length did not work',
|
||||
golden, hash.digest());
|
||||
|
||||
// Test array and string inputs.
|
||||
hash.reset();
|
||||
hash.update([97, 66]);
|
||||
golden = hash.digest();
|
||||
hash.reset();
|
||||
hash.update('aB');
|
||||
assertArrayEquals('String and array inputs should give the same result',
|
||||
golden, hash.digest());
|
||||
|
||||
// Empty hash.
|
||||
hash.reset();
|
||||
var empty = hash.digest();
|
||||
assertTrue('Empty hash collided with a non-trivial one',
|
||||
!!goog.testing.asserts.findDifferences(golden, empty));
|
||||
|
||||
// Zero-length array update.
|
||||
hash.reset();
|
||||
hash.update([]);
|
||||
assertArrayEquals('Updating with an empty array did not give an empty hash',
|
||||
empty, hash.digest());
|
||||
|
||||
// Zero-length string update.
|
||||
hash.reset();
|
||||
hash.update('');
|
||||
assertArrayEquals('Updating with an empty string did not give an empty hash',
|
||||
empty, hash.digest());
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 Implementation of HMAC in JavaScript.
|
||||
*
|
||||
* Usage:
|
||||
* var hmac = new goog.crypt.Hmac(new goog.crypt.sha1(), key, 64);
|
||||
* var digest = hmac.getHmac(bytes);
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.crypt.Hmac');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.crypt.Hash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {!goog.crypt.Hash} hasher An object to serve as a hash function.
|
||||
* @param {Array.<number>} key The secret key to use to calculate the hmac.
|
||||
* Should be an array of not more than {@code blockSize} integers in
|
||||
{0, 255}.
|
||||
* @param {number=} opt_blockSize Optional. The block size {@code hasher} uses.
|
||||
* If not specified, 16.
|
||||
* @extends {goog.crypt.Hash}
|
||||
*/
|
||||
goog.crypt.Hmac = function(hasher, key, opt_blockSize) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* The underlying hasher to calculate hash.
|
||||
*
|
||||
* @type {!goog.crypt.Hash}
|
||||
* @private
|
||||
*/
|
||||
this.hasher_ = hasher;
|
||||
|
||||
/**
|
||||
* The block size.
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.blockSize_ = opt_blockSize || 16;
|
||||
|
||||
/**
|
||||
* The outer padding array of hmac
|
||||
*
|
||||
* @type {!Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.keyO_ = new Array(this.blockSize_);
|
||||
|
||||
/**
|
||||
* The inner padding array of hmac
|
||||
*
|
||||
* @type {!Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.keyI_ = new Array(this.blockSize_);
|
||||
|
||||
this.initialize_(key);
|
||||
};
|
||||
goog.inherits(goog.crypt.Hmac, goog.crypt.Hash);
|
||||
|
||||
|
||||
/**
|
||||
* Outer padding byte of HMAC algorith, per http://en.wikipedia.org/wiki/HMAC
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.Hmac.OPAD_ = 0x5c;
|
||||
|
||||
|
||||
/**
|
||||
* Inner padding byte of HMAC algorith, per http://en.wikipedia.org/wiki/HMAC
|
||||
*
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.Hmac.IPAD_ = 0x36;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes Hmac by precalculating the inner and outer paddings.
|
||||
*
|
||||
* @param {Array.<number>} key The secret key to use to calculate the hmac.
|
||||
* Should be an array of not more than {@code blockSize} integers in
|
||||
{0, 255}.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.Hmac.prototype.initialize_ = function(key) {
|
||||
if (key.length > this.blockSize_) {
|
||||
this.hasher_.update(key);
|
||||
key = this.hasher_.digest();
|
||||
}
|
||||
// Precalculate padded and xor'd keys.
|
||||
var keyByte;
|
||||
for (var i = 0; i < this.blockSize_; i++) {
|
||||
if (i < key.length) {
|
||||
keyByte = key[i];
|
||||
} else {
|
||||
keyByte = 0;
|
||||
}
|
||||
this.keyO_[i] = keyByte ^ goog.crypt.Hmac.OPAD_;
|
||||
this.keyI_[i] = keyByte ^ goog.crypt.Hmac.IPAD_;
|
||||
}
|
||||
// Be ready for an immediate update.
|
||||
this.hasher_.update(this.keyI_);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Hmac.prototype.reset = function() {
|
||||
this.hasher_.reset();
|
||||
this.hasher_.update(this.keyI_);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Hmac.prototype.update = function(bytes, opt_length) {
|
||||
this.hasher_.update(bytes, opt_length);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Hmac.prototype.digest = function() {
|
||||
var temp = this.hasher_.digest();
|
||||
this.hasher_.reset();
|
||||
this.hasher_.update(this.keyO_);
|
||||
this.hasher_.update(temp);
|
||||
return this.hasher_.digest();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates an HMAC for a given message.
|
||||
*
|
||||
* @param {Array.<number>} message An array of integers in {0, 255}.
|
||||
* @return {Array} the digest of the given message.
|
||||
*/
|
||||
goog.crypt.Hmac.prototype.getHmac = function(message) {
|
||||
this.reset();
|
||||
this.update(message);
|
||||
return this.digest();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
// 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 MD5 cryptographic hash.
|
||||
* Implementation of http://tools.ietf.org/html/rfc1321 with common
|
||||
* optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5).
|
||||
*
|
||||
* Usage:
|
||||
* var md5 = new goog.crypt.Md5();
|
||||
* md5.update(bytes);
|
||||
* var hash = md5.digest();
|
||||
*
|
||||
* Performance:
|
||||
* Chrome 14 ~450 Mbit/s
|
||||
* Chrome 13 (in a VM) ~250 Mbit/s
|
||||
* Firefox 6.0 (in a VM) ~100 Mbit/s
|
||||
* IE9 (in a VM) ~27 Mbit/s
|
||||
* Firefox 3.6 ~15 Mbit/s
|
||||
* IE8 (in a VM) ~13 Mbit/s
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.Md5');
|
||||
|
||||
goog.require('goog.crypt.Hash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* MD5 cryptographic hash constructor.
|
||||
* @constructor
|
||||
* @extends {goog.crypt.Hash}
|
||||
*/
|
||||
goog.crypt.Md5 = function() {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Holds the current values of accumulated A-D variables (MD buffer).
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.chain_ = new Array(4);
|
||||
|
||||
/**
|
||||
* A buffer holding the data until the whole block can be processed.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.block_ = new Array(64);
|
||||
|
||||
/**
|
||||
* The length of yet-unprocessed data as collected in the block.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.blockLength_ = 0;
|
||||
|
||||
/**
|
||||
* The total length of the message so far.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.totalLength_ = 0;
|
||||
|
||||
this.reset();
|
||||
};
|
||||
goog.inherits(goog.crypt.Md5, goog.crypt.Hash);
|
||||
|
||||
|
||||
/**
|
||||
* Integer rotation constants used by the abbreviated implementation.
|
||||
* They are hardcoded in the unrolled implementation, so it is left
|
||||
* here commented out.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*
|
||||
goog.crypt.Md5.S_ = [
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
|
||||
];
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sine function constants used by the abbreviated implementation.
|
||||
* They are hardcoded in the unrolled implementation, so it is left
|
||||
* here commented out.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*
|
||||
goog.crypt.Md5.T_ = [
|
||||
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
|
||||
];
|
||||
*/
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Md5.prototype.reset = function() {
|
||||
this.chain_[0] = 0x67452301;
|
||||
this.chain_[1] = 0xefcdab89;
|
||||
this.chain_[2] = 0x98badcfe;
|
||||
this.chain_[3] = 0x10325476;
|
||||
|
||||
this.blockLength_ = 0;
|
||||
this.totalLength_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal compress helper function. It takes a block of data (64 bytes)
|
||||
* and updates the accumulator.
|
||||
* @param {Array.<number>|Uint8Array|string} buf The block to compress.
|
||||
* @param {number=} opt_offset Offset of the block in the buffer.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) {
|
||||
if (!opt_offset) {
|
||||
opt_offset = 0;
|
||||
}
|
||||
|
||||
// We allocate the array every time, but it's cheap in practice.
|
||||
var X = new Array(16);
|
||||
|
||||
// Get 16 little endian words. It is not worth unrolling this for Chrome 11.
|
||||
if (goog.isString(buf)) {
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
X[i] = (buf.charCodeAt(opt_offset++)) |
|
||||
(buf.charCodeAt(opt_offset++) << 8) |
|
||||
(buf.charCodeAt(opt_offset++) << 16) |
|
||||
(buf.charCodeAt(opt_offset++) << 24);
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
X[i] = (buf[opt_offset++]) |
|
||||
(buf[opt_offset++] << 8) |
|
||||
(buf[opt_offset++] << 16) |
|
||||
(buf[opt_offset++] << 24);
|
||||
}
|
||||
}
|
||||
|
||||
var A = this.chain_[0];
|
||||
var B = this.chain_[1];
|
||||
var C = this.chain_[2];
|
||||
var D = this.chain_[3];
|
||||
var sum = 0;
|
||||
|
||||
/*
|
||||
* This is an abbreviated implementation, it is left here commented out for
|
||||
* reference purposes. See below for an unrolled version in use.
|
||||
*
|
||||
var f, n, tmp;
|
||||
for (var i = 0; i < 64; ++i) {
|
||||
|
||||
if (i < 16) {
|
||||
f = (D ^ (B & (C ^ D)));
|
||||
n = i;
|
||||
} else if (i < 32) {
|
||||
f = (C ^ (D & (B ^ C)));
|
||||
n = (5 * i + 1) % 16;
|
||||
} else if (i < 48) {
|
||||
f = (B ^ C ^ D);
|
||||
n = (3 * i + 5) % 16;
|
||||
} else {
|
||||
f = (C ^ (B | (~D)));
|
||||
n = (7 * i) % 16;
|
||||
}
|
||||
|
||||
tmp = D;
|
||||
D = C;
|
||||
C = B;
|
||||
sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff;
|
||||
B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) |
|
||||
(sum >>> (32 - goog.crypt.Md5.S_[i]));
|
||||
A = tmp;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an unrolled MD5 implementation, which gives ~30% speedup compared
|
||||
* to the abbreviated implementation above, as measured on Chrome 11. It is
|
||||
* important to keep 32-bit croppings to minimum and inline the integer
|
||||
* rotation.
|
||||
*/
|
||||
sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff;
|
||||
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
|
||||
sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff;
|
||||
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
|
||||
sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff;
|
||||
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
|
||||
sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff;
|
||||
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
|
||||
sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff;
|
||||
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
|
||||
sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff;
|
||||
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
|
||||
sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff;
|
||||
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
|
||||
sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff;
|
||||
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
|
||||
sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff;
|
||||
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
|
||||
sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff;
|
||||
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
|
||||
sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff;
|
||||
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
|
||||
sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff;
|
||||
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
|
||||
sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff;
|
||||
A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
|
||||
sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff;
|
||||
D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
|
||||
sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff;
|
||||
C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
|
||||
sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff;
|
||||
B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
|
||||
sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff;
|
||||
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
|
||||
sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff;
|
||||
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
|
||||
sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff;
|
||||
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
|
||||
sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff;
|
||||
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
|
||||
sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff;
|
||||
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
|
||||
sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff;
|
||||
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
|
||||
sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff;
|
||||
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
|
||||
sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff;
|
||||
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
|
||||
sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff;
|
||||
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
|
||||
sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff;
|
||||
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
|
||||
sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff;
|
||||
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
|
||||
sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff;
|
||||
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
|
||||
sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff;
|
||||
A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
|
||||
sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff;
|
||||
D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
|
||||
sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff;
|
||||
C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
|
||||
sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff;
|
||||
B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
|
||||
sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff;
|
||||
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
|
||||
sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff;
|
||||
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
|
||||
sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff;
|
||||
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
|
||||
sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff;
|
||||
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
|
||||
sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff;
|
||||
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
|
||||
sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff;
|
||||
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
|
||||
sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff;
|
||||
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
|
||||
sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff;
|
||||
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
|
||||
sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff;
|
||||
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
|
||||
sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff;
|
||||
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
|
||||
sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff;
|
||||
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
|
||||
sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff;
|
||||
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
|
||||
sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff;
|
||||
A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
|
||||
sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff;
|
||||
D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
|
||||
sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff;
|
||||
C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
|
||||
sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff;
|
||||
B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
|
||||
sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff;
|
||||
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
|
||||
sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff;
|
||||
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
|
||||
sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff;
|
||||
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
|
||||
sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff;
|
||||
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
|
||||
sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff;
|
||||
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
|
||||
sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff;
|
||||
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
|
||||
sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff;
|
||||
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
|
||||
sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff;
|
||||
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
|
||||
sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff;
|
||||
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
|
||||
sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff;
|
||||
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
|
||||
sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff;
|
||||
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
|
||||
sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff;
|
||||
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
|
||||
sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff;
|
||||
A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
|
||||
sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff;
|
||||
D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
|
||||
sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff;
|
||||
C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
|
||||
sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff;
|
||||
B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
|
||||
|
||||
this.chain_[0] = (this.chain_[0] + A) & 0xffffffff;
|
||||
this.chain_[1] = (this.chain_[1] + B) & 0xffffffff;
|
||||
this.chain_[2] = (this.chain_[2] + C) & 0xffffffff;
|
||||
this.chain_[3] = (this.chain_[3] + D) & 0xffffffff;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Md5.prototype.update = function(bytes, opt_length) {
|
||||
if (!goog.isDef(opt_length)) {
|
||||
opt_length = bytes.length;
|
||||
}
|
||||
var lengthMinusBlock = opt_length - 64;
|
||||
|
||||
// Copy some object properties to local variables in order to save on access
|
||||
// time from inside the loop (~10% speedup was observed on Chrome 11).
|
||||
var block = this.block_;
|
||||
var blockLength = this.blockLength_;
|
||||
var i = 0;
|
||||
|
||||
// The outer while loop should execute at most twice.
|
||||
while (i < opt_length) {
|
||||
// When we have no data in the block to top up, we can directly process the
|
||||
// input buffer (assuming it contains sufficient data). This gives ~30%
|
||||
// speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that
|
||||
// the data is provided in large chunks (or in multiples of 64 bytes).
|
||||
if (blockLength == 0) {
|
||||
while (i <= lengthMinusBlock) {
|
||||
this.compress_(bytes, i);
|
||||
i += 64;
|
||||
}
|
||||
}
|
||||
if (goog.isString(bytes)) {
|
||||
while (i < opt_length) {
|
||||
block[blockLength++] = bytes.charCodeAt(i++);
|
||||
if (blockLength == 64) {
|
||||
this.compress_(block);
|
||||
blockLength = 0;
|
||||
// Jump to the outer loop so we use the full-block optimization.
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (i < opt_length) {
|
||||
block[blockLength++] = bytes[i++];
|
||||
if (blockLength == 64) {
|
||||
this.compress_(block);
|
||||
blockLength = 0;
|
||||
// Jump to the outer loop so we use the full-block optimization.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.blockLength_ = blockLength;
|
||||
this.totalLength_ += opt_length;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Md5.prototype.digest = function() {
|
||||
// This must accommodate at least 1 padding byte (0x80), 8 bytes of
|
||||
// total bitlength, and must end at a 64-byte boundary.
|
||||
var pad = new Array((this.blockLength_ < 56 ? 64 : 128) - this.blockLength_);
|
||||
|
||||
// Add padding: 0x80 0x00*
|
||||
pad[0] = 0x80;
|
||||
for (var i = 1; i < pad.length - 8; ++i) {
|
||||
pad[i] = 0;
|
||||
}
|
||||
// Add the total number of bits, little endian 64-bit integer.
|
||||
var totalBits = this.totalLength_ * 8;
|
||||
for (var i = pad.length - 8; i < pad.length; ++i) {
|
||||
pad[i] = totalBits & 0xff;
|
||||
totalBits /= 0x100; // Don't use bit-shifting here!
|
||||
}
|
||||
this.update(pad);
|
||||
|
||||
var digest = new Array(16);
|
||||
var n = 0;
|
||||
for (var i = 0; i < 4; ++i) {
|
||||
for (var j = 0; j < 32; j += 8) {
|
||||
digest[n++] = (this.chain_[i] >>> j) & 0xff;
|
||||
}
|
||||
}
|
||||
return digest;
|
||||
};
|
||||
@@ -0,0 +1,232 @@
|
||||
// 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 SHA-1 cryptographic hash.
|
||||
* Variable names follow the notation in FIPS PUB 180-3:
|
||||
* http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf.
|
||||
*
|
||||
* Usage:
|
||||
* var sha1 = new goog.crypt.sha1();
|
||||
* sha1.update(bytes);
|
||||
* var hash = sha1.digest();
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.crypt.Sha1');
|
||||
|
||||
goog.require('goog.crypt.Hash');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SHA-1 cryptographic hash constructor.
|
||||
*
|
||||
* The properties declared here are discussed in the above algorithm document.
|
||||
* @constructor
|
||||
* @extends {goog.crypt.Hash}
|
||||
*/
|
||||
goog.crypt.Sha1 = function() {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Holds the previous values of accumulated variables a-e in the compress_
|
||||
* function.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.chain_ = [];
|
||||
|
||||
/**
|
||||
* A buffer holding the partially computed hash result.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.buf_ = [];
|
||||
|
||||
/**
|
||||
* An array of 80 bytes, each a part of the message to be hashed. Referred to
|
||||
* as the message schedule in the docs.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.W_ = [];
|
||||
|
||||
/**
|
||||
* Contains data needed to pad messages less than 64 bytes.
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.pad_ = [];
|
||||
|
||||
this.pad_[0] = 128;
|
||||
for (var i = 1; i < 64; ++i) {
|
||||
this.pad_[i] = 0;
|
||||
}
|
||||
|
||||
this.reset();
|
||||
};
|
||||
goog.inherits(goog.crypt.Sha1, goog.crypt.Hash);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Sha1.prototype.reset = function() {
|
||||
this.chain_[0] = 0x67452301;
|
||||
this.chain_[1] = 0xefcdab89;
|
||||
this.chain_[2] = 0x98badcfe;
|
||||
this.chain_[3] = 0x10325476;
|
||||
this.chain_[4] = 0xc3d2e1f0;
|
||||
|
||||
this.inbuf_ = 0;
|
||||
this.total_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal compress helper function.
|
||||
* @param {Array.<number>} buf Buffer with the block to compress.
|
||||
* @param {number=} opt_offset Offset of the block in the buffer.
|
||||
* @private
|
||||
*/
|
||||
goog.crypt.Sha1.prototype.compress_ = function(buf, opt_offset) {
|
||||
if (!opt_offset) {
|
||||
opt_offset = 0;
|
||||
}
|
||||
|
||||
var W = this.W_;
|
||||
|
||||
// get 16 big endian words
|
||||
for (var i = opt_offset; i < opt_offset + 64; i += 4) {
|
||||
var w = (buf[i] << 24) |
|
||||
(buf[i + 1] << 16) |
|
||||
(buf[i + 2] << 8) |
|
||||
(buf[i + 3]);
|
||||
W[i / 4] = w;
|
||||
}
|
||||
|
||||
// expand to 80 words
|
||||
for (var i = 16; i < 80; i++) {
|
||||
var t = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
|
||||
W[i] = ((t << 1) | (t >>> 31)) & 0xffffffff;
|
||||
}
|
||||
|
||||
var a = this.chain_[0];
|
||||
var b = this.chain_[1];
|
||||
var c = this.chain_[2];
|
||||
var d = this.chain_[3];
|
||||
var e = this.chain_[4];
|
||||
var f, k;
|
||||
|
||||
// TODO(user): Try to unroll this loop to speed up the computation.
|
||||
for (var i = 0; i < 80; i++) {
|
||||
if (i < 40) {
|
||||
if (i < 20) {
|
||||
f = d ^ (b & (c ^ d));
|
||||
k = 0x5a827999;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ed9eba1;
|
||||
}
|
||||
} else {
|
||||
if (i < 60) {
|
||||
f = (b & c) | (d & (b | c));
|
||||
k = 0x8f1bbcdc;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xca62c1d6;
|
||||
}
|
||||
}
|
||||
|
||||
var t = (((a << 5) | (a >>> 27)) + f + e + k + W[i]) & 0xffffffff;
|
||||
e = d;
|
||||
d = c;
|
||||
c = ((b << 30) | (b >>> 2)) & 0xffffffff;
|
||||
b = a;
|
||||
a = t;
|
||||
}
|
||||
|
||||
this.chain_[0] = (this.chain_[0] + a) & 0xffffffff;
|
||||
this.chain_[1] = (this.chain_[1] + b) & 0xffffffff;
|
||||
this.chain_[2] = (this.chain_[2] + c) & 0xffffffff;
|
||||
this.chain_[3] = (this.chain_[3] + d) & 0xffffffff;
|
||||
this.chain_[4] = (this.chain_[4] + e) & 0xffffffff;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Sha1.prototype.update = function(bytes, opt_length) {
|
||||
if (!goog.isDef(opt_length)) {
|
||||
opt_length = bytes.length;
|
||||
}
|
||||
|
||||
var buf = this.buf_;
|
||||
var inbuf = this.inbuf_;
|
||||
var n = 0;
|
||||
|
||||
// Strangely enough, it is faster to copy the data than to pass over the
|
||||
// buffer and an offset. Copying in a loop is also as fast as array slicing.
|
||||
// This was tested on Chrome 11 and Firefox 3.6. Please do not optimize
|
||||
// the following without careful profiling.
|
||||
if (goog.isString(bytes)) {
|
||||
while (n < opt_length) {
|
||||
buf[inbuf++] = bytes.charCodeAt(n++);
|
||||
if (inbuf == 64) {
|
||||
this.compress_(buf);
|
||||
inbuf = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (n < opt_length) {
|
||||
buf[inbuf++] = bytes[n++];
|
||||
if (inbuf == 64) {
|
||||
this.compress_(buf);
|
||||
inbuf = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.inbuf_ = inbuf;
|
||||
this.total_ += opt_length;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.crypt.Sha1.prototype.digest = function() {
|
||||
var digest = [];
|
||||
var totalBits = this.total_ * 8;
|
||||
|
||||
// Add pad 0x80 0x00*.
|
||||
if (this.inbuf_ < 56) {
|
||||
this.update(this.pad_, 56 - this.inbuf_);
|
||||
} else {
|
||||
this.update(this.pad_, 64 - (this.inbuf_ - 56));
|
||||
}
|
||||
|
||||
// Add # bits.
|
||||
for (var i = 63; i >= 56; i--) {
|
||||
this.buf_[i] = totalBits & 255;
|
||||
totalBits /= 256; // Don't use bit-shifting here!
|
||||
}
|
||||
|
||||
this.compress_(this.buf_);
|
||||
|
||||
var n = 0;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
for (var j = 24; j >= 0; j -= 8) {
|
||||
digest[n++] = (this.chain_[i] >> j) & 255;
|
||||
}
|
||||
}
|
||||
|
||||
return digest;
|
||||
};
|
||||
@@ -0,0 +1,447 @@
|
||||
// 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 CSS Object Model helper functions.
|
||||
* References:
|
||||
* - W3C: http://dev.w3.org/csswg/cssom/
|
||||
* - MSDN: http://msdn.microsoft.com/en-us/library/ms531209(VS.85).aspx.
|
||||
* @supported in FF3, IE6, IE7, Safari 3.1.2, Chrome
|
||||
* TODO(user): Fix in Opera.
|
||||
* TODO(user): Consider hacking page, media, etc.. to work.
|
||||
* This would be pretty challenging. IE returns the text for any rule
|
||||
* regardless of whether or not the media is correct or not. Firefox at
|
||||
* least supports CSSRule.type to figure out if it's a media type and then
|
||||
* we could do something interesting, but IE offers no way for us to tell.
|
||||
*/
|
||||
|
||||
goog.provide('goog.cssom');
|
||||
goog.provide('goog.cssom.CssRuleType');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of {@code CSSRule} types.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.cssom.CssRuleType = {
|
||||
STYLE: 1,
|
||||
IMPORT: 3,
|
||||
MEDIA: 4,
|
||||
FONT_FACE: 5,
|
||||
PAGE: 6,
|
||||
NAMESPACE: 7
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively gets all CSS as text, optionally starting from a given
|
||||
* CSSStyleSheet.
|
||||
* @param {CSSStyleSheet=} opt_styleSheet The CSSStyleSheet.
|
||||
* @return {string} css text.
|
||||
*/
|
||||
goog.cssom.getAllCssText = function(opt_styleSheet) {
|
||||
var styleSheet = opt_styleSheet || document.styleSheets;
|
||||
return /** @type {string} */ (goog.cssom.getAllCss_(styleSheet, true));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively gets all CSSStyleRules, optionally starting from a given
|
||||
* CSSStyleSheet.
|
||||
* Note that this excludes any CSSImportRules, CSSMediaRules, etc..
|
||||
* @param {CSSStyleSheet=} opt_styleSheet The CSSStyleSheet.
|
||||
* @return {Array.<CSSStyleRule>} A list of CSSStyleRules.
|
||||
*/
|
||||
goog.cssom.getAllCssStyleRules = function(opt_styleSheet) {
|
||||
var styleSheet = opt_styleSheet || document.styleSheets;
|
||||
return /** @type {Array.<CSSStyleRule>} */ (
|
||||
goog.cssom.getAllCss_(styleSheet, false));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the CSSRules from a styleSheet.
|
||||
* Worth noting here is that IE and FF differ in terms of what they will return.
|
||||
* Firefox will return styleSheet.cssRules, which includes ImportRules and
|
||||
* anything which implements the CSSRules interface. IE returns simply a list of
|
||||
* CSSRules.
|
||||
* @param {CSSStyleSheet} styleSheet The CSSStyleSheet.
|
||||
* @throws {Error} If we cannot access the rules on a stylesheet object - this
|
||||
* can happen if a stylesheet object's rules are accessed before the rules
|
||||
* have been downloaded and parsed and are "ready".
|
||||
* @return {CSSRuleList} An array of CSSRules or null.
|
||||
*/
|
||||
goog.cssom.getCssRulesFromStyleSheet = function(styleSheet) {
|
||||
var cssRuleList = null;
|
||||
try {
|
||||
// IE is .rules, W3c is cssRules.
|
||||
cssRuleList = styleSheet.rules || styleSheet.cssRules;
|
||||
} catch (e) {
|
||||
// This can happen if we try to access the CSSOM before it's "ready".
|
||||
if (e.code == 15) {
|
||||
// Firefox throws an NS_ERROR_DOM_INVALID_ACCESS_ERR error if a stylesheet
|
||||
// is read before it has been fully parsed. Let the caller know which
|
||||
// stylesheet failed.
|
||||
e.styleSheet = styleSheet;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return cssRuleList;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all CSSStyleSheet objects starting from some CSSStyleSheet. Note that we
|
||||
* want to return the sheets in the order of the cascade, therefore if we
|
||||
* encounter an import, we will splice that CSSStyleSheet object in front of
|
||||
* the CSSStyleSheet that contains it in the returned array of CSSStyleSheets.
|
||||
* @param {CSSStyleSheet=} opt_styleSheet A CSSStyleSheet.
|
||||
* @param {boolean=} opt_includeDisabled If true, includes disabled stylesheets,
|
||||
* defaults to false.
|
||||
* @return {Array.<CSSStyleSheet>} A list of CSSStyleSheet objects.
|
||||
*/
|
||||
goog.cssom.getAllCssStyleSheets = function(opt_styleSheet,
|
||||
opt_includeDisabled) {
|
||||
var styleSheetsOutput = [];
|
||||
var styleSheet = opt_styleSheet || document.styleSheets;
|
||||
var includeDisabled = goog.isDef(opt_includeDisabled) ? opt_includeDisabled :
|
||||
false;
|
||||
|
||||
// Imports need to go first.
|
||||
if (styleSheet.imports && styleSheet.imports.length) {
|
||||
for (var i = 0, n = styleSheet.imports.length; i < n; i++) {
|
||||
goog.array.extend(styleSheetsOutput,
|
||||
goog.cssom.getAllCssStyleSheets(styleSheet.imports[i]));
|
||||
}
|
||||
|
||||
} else if (styleSheet.length) {
|
||||
// In case we get a StyleSheetList object.
|
||||
// http://dev.w3.org/csswg/cssom/#the-stylesheetlist
|
||||
for (var i = 0, n = styleSheet.length; i < n; i++) {
|
||||
goog.array.extend(styleSheetsOutput,
|
||||
goog.cssom.getAllCssStyleSheets(styleSheet[i]));
|
||||
}
|
||||
} else {
|
||||
// We need to walk through rules in browsers which implement .cssRules
|
||||
// to see if there are styleSheets buried in there.
|
||||
// If we have a CSSStyleSheet within CssRules.
|
||||
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
|
||||
if (cssRuleList && cssRuleList.length) {
|
||||
// Chrome does not evaluate cssRuleList[i] to undefined when i >=n;
|
||||
// so we use a (i < n) check instead of cssRuleList[i] in the loop below
|
||||
// and in other places where we iterate over a rules list.
|
||||
// See issue # 5917 in Chromium.
|
||||
for (var i = 0, n = cssRuleList.length, cssRule; i < n; i++) {
|
||||
cssRule = cssRuleList[i];
|
||||
// There are more stylesheets to get on this object..
|
||||
if (cssRule.styleSheet) {
|
||||
goog.array.extend(styleSheetsOutput,
|
||||
goog.cssom.getAllCssStyleSheets(cssRule.styleSheet));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a CSSStyleSheet. (IE uses .rules, W3c and Opera cssRules.)
|
||||
if ((styleSheet.type || styleSheet.rules || styleSheet.cssRules) &&
|
||||
(!styleSheet.disabled || includeDisabled)) {
|
||||
styleSheetsOutput.push(styleSheet);
|
||||
}
|
||||
|
||||
return styleSheetsOutput;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the cssText from a CSSRule object cross-browserly.
|
||||
* @param {CSSRule} cssRule A CSSRule.
|
||||
* @return {string} cssText The text for the rule, including the selector.
|
||||
*/
|
||||
goog.cssom.getCssTextFromCssRule = function(cssRule) {
|
||||
var cssText = '';
|
||||
|
||||
if (cssRule.cssText) {
|
||||
// W3C.
|
||||
cssText = cssRule.cssText;
|
||||
} else if (cssRule.style && cssRule.style.cssText && cssRule.selectorText) {
|
||||
// IE: The spacing here is intended to make the result consistent with
|
||||
// FF and Webkit.
|
||||
// We also remove the special properties that we may have added in
|
||||
// getAllCssStyleRules since IE includes those in the cssText.
|
||||
var styleCssText = cssRule.style.cssText.
|
||||
replace(/\s*-closure-parent-stylesheet:\s*\[object\];?\s*/gi, '').
|
||||
replace(/\s*-closure-rule-index:\s*[\d]+;?\s*/gi, '');
|
||||
var thisCssText = cssRule.selectorText + ' { ' + styleCssText + ' }';
|
||||
cssText = thisCssText;
|
||||
}
|
||||
|
||||
return cssText;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the index of the CSSRule in it's CSSStyleSheet.
|
||||
* @param {CSSRule} cssRule A CSSRule.
|
||||
* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
|
||||
* object this cssRule belongs to.
|
||||
* @throws {Error} When we cannot get the parentStyleSheet.
|
||||
* @return {number} The index of the CSSRule, or -1.
|
||||
*/
|
||||
goog.cssom.getCssRuleIndexInParentStyleSheet = function(cssRule,
|
||||
opt_parentStyleSheet) {
|
||||
// Look for our special style.ruleIndex property from getAllCss.
|
||||
if (cssRule.style && cssRule.style['-closure-rule-index']) {
|
||||
return cssRule.style['-closure-rule-index'];
|
||||
}
|
||||
|
||||
var parentStyleSheet = opt_parentStyleSheet ||
|
||||
goog.cssom.getParentStyleSheet(cssRule);
|
||||
|
||||
if (!parentStyleSheet) {
|
||||
// We could call getAllCssStyleRules() here to get our special indexes on
|
||||
// the style object, but that seems like it could be wasteful.
|
||||
throw Error('Cannot find a parentStyleSheet.');
|
||||
}
|
||||
|
||||
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(parentStyleSheet);
|
||||
if (cssRuleList && cssRuleList.length) {
|
||||
for (var i = 0, n = cssRuleList.length, thisCssRule; i < n; i++) {
|
||||
thisCssRule = cssRuleList[i];
|
||||
if (thisCssRule == cssRule) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* We do some trickery in getAllCssStyleRules that hacks this in for IE.
|
||||
* If the cssRule object isn't coming from a result of that function call, this
|
||||
* method will return undefined in IE.
|
||||
* @param {CSSRule} cssRule The CSSRule.
|
||||
* @return {CSSStyleSheet} A styleSheet object.
|
||||
*/
|
||||
goog.cssom.getParentStyleSheet = function(cssRule) {
|
||||
return cssRule.parentStyleSheet ||
|
||||
cssRule.style['-closure-parent-stylesheet'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replace a cssRule with some cssText for a new rule.
|
||||
* If the cssRule object is not one of objects returned by
|
||||
* getAllCssStyleRules, then you'll need to provide both the styleSheet and
|
||||
* possibly the index, since we can't infer them from the standard cssRule
|
||||
* object in IE. We do some trickery in getAllCssStyleRules to hack this in.
|
||||
* @param {CSSRule} cssRule A CSSRule.
|
||||
* @param {string} cssText The text for the new CSSRule.
|
||||
* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
|
||||
* object this cssRule belongs to.
|
||||
* @param {number=} opt_index The index of the cssRule in its parentStylesheet.
|
||||
* @throws {Error} If we cannot find a parentStyleSheet.
|
||||
* @throws {Error} If we cannot find a css rule index.
|
||||
*/
|
||||
goog.cssom.replaceCssRule = function(cssRule, cssText, opt_parentStyleSheet,
|
||||
opt_index) {
|
||||
var parentStyleSheet = opt_parentStyleSheet ||
|
||||
goog.cssom.getParentStyleSheet(cssRule);
|
||||
if (parentStyleSheet) {
|
||||
var index = opt_index >= 0 ? opt_index :
|
||||
goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule, parentStyleSheet);
|
||||
if (index >= 0) {
|
||||
goog.cssom.removeCssRule(parentStyleSheet, index);
|
||||
goog.cssom.addCssRule(parentStyleSheet, cssText, index);
|
||||
} else {
|
||||
throw Error('Cannot proceed without the index of the cssRule.');
|
||||
}
|
||||
} else {
|
||||
throw Error('Cannot proceed without the parentStyleSheet.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cross browser function to add a CSSRule into a CSSStyleSheet, optionally
|
||||
* at a given index.
|
||||
* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
|
||||
* @param {string} cssText The text for the new CSSRule.
|
||||
* @param {number=} opt_index The index of the cssRule in its parentStylesheet.
|
||||
* @throws {Error} If the css rule text appears to be ill-formatted.
|
||||
* TODO(bowdidge): Inserting at index 0 fails on Firefox 2 and 3 with an
|
||||
* exception warning "Node cannot be inserted at the specified point in
|
||||
* the hierarchy."
|
||||
*/
|
||||
goog.cssom.addCssRule = function(cssStyleSheet, cssText, opt_index) {
|
||||
var index = opt_index;
|
||||
if (index < 0 || index == undefined) {
|
||||
// If no index specified, insert at the end of the current list
|
||||
// of rules.
|
||||
// If on IE, use rules property, otherwise use cssRules property.
|
||||
var rules = cssStyleSheet.rules || cssStyleSheet.cssRules;
|
||||
index = rules.length;
|
||||
}
|
||||
if (cssStyleSheet.insertRule) {
|
||||
// W3C.
|
||||
cssStyleSheet.insertRule(cssText, index);
|
||||
|
||||
} else {
|
||||
// IE: We have to parse the cssRule text to get the selector separated
|
||||
// from the style text.
|
||||
// aka Everything that isn't a colon, followed by a colon, then
|
||||
// the rest is the style part.
|
||||
var matches = /^([^\{]+)\{([^\{]+)\}/.exec(cssText);
|
||||
if (matches.length == 3) {
|
||||
var selector = matches[1];
|
||||
var style = matches[2];
|
||||
cssStyleSheet.addRule(selector, style, index);
|
||||
} else {
|
||||
throw Error('Your CSSRule appears to be ill-formatted.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cross browser function to remove a CSSRule in a CSSStyleSheet at an index.
|
||||
* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
|
||||
* @param {number} index The CSSRule's index in the parentStyleSheet.
|
||||
*/
|
||||
goog.cssom.removeCssRule = function(cssStyleSheet, index) {
|
||||
if (cssStyleSheet.deleteRule) {
|
||||
// W3C.
|
||||
cssStyleSheet.deleteRule(index);
|
||||
|
||||
} else {
|
||||
// IE.
|
||||
cssStyleSheet.removeRule(index);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Appends a DOM node to HEAD containing the css text that's passed in.
|
||||
* @param {string} cssText CSS to add to the end of the document.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper user for
|
||||
* document interactions.
|
||||
* @return {Element} The newly created STYLE element.
|
||||
*/
|
||||
goog.cssom.addCssText = function(cssText, opt_domHelper) {
|
||||
var document = opt_domHelper ? opt_domHelper.getDocument() :
|
||||
goog.dom.getDocument();
|
||||
var cssNode = document.createElement('style');
|
||||
cssNode.type = 'text/css';
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
head.appendChild(cssNode);
|
||||
if (cssNode.styleSheet) {
|
||||
// IE.
|
||||
cssNode.styleSheet.cssText = cssText;
|
||||
} else {
|
||||
// W3C.
|
||||
var cssTextNode = document.createTextNode(cssText);
|
||||
cssNode.appendChild(cssTextNode);
|
||||
}
|
||||
return cssNode;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cross browser method to get the filename from the StyleSheet's href.
|
||||
* Explorer only returns the filename in the href, while other agents return
|
||||
* the full path.
|
||||
* @param {!StyleSheet} styleSheet Any valid StyleSheet object with an href.
|
||||
* @throws {Error} When there's no href property found.
|
||||
* @return {?string} filename The filename, or null if not an external
|
||||
* styleSheet.
|
||||
*/
|
||||
goog.cssom.getFileNameFromStyleSheet = function(styleSheet) {
|
||||
var href = styleSheet.href;
|
||||
|
||||
// Another IE/FF difference. IE returns an empty string, while FF and others
|
||||
// return null for CSSStyleSheets not from an external file.
|
||||
if (!href) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We need the regexp to ensure we get the filename minus any query params.
|
||||
var matches = /([^\/\?]+)[^\/]*$/.exec(href);
|
||||
var filename = matches[1];
|
||||
return filename;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively gets all CSS text or rules.
|
||||
* @param {CSSStyleSheet} styleSheet The CSSStyleSheet.
|
||||
* @param {boolean} isTextOutput If true, output is cssText, otherwise cssRules.
|
||||
* @return {string|Array.<CSSRule>} cssText or cssRules.
|
||||
* @private
|
||||
*/
|
||||
goog.cssom.getAllCss_ = function(styleSheet, isTextOutput) {
|
||||
var cssOut = [];
|
||||
var styleSheets = goog.cssom.getAllCssStyleSheets(styleSheet);
|
||||
|
||||
for (var i = 0; styleSheet = styleSheets[i]; i++) {
|
||||
var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
|
||||
|
||||
if (cssRuleList && cssRuleList.length) {
|
||||
|
||||
// We're going to track cssRule index if we want rule output.
|
||||
if (!isTextOutput) {
|
||||
var ruleIndex = 0;
|
||||
}
|
||||
|
||||
for (var j = 0, n = cssRuleList.length, cssRule; j < n; j++) {
|
||||
cssRule = cssRuleList[j];
|
||||
// Gets cssText output, ignoring CSSImportRules.
|
||||
if (isTextOutput && !cssRule.href) {
|
||||
var res = goog.cssom.getCssTextFromCssRule(cssRule);
|
||||
cssOut.push(res);
|
||||
|
||||
} else if (!cssRule.href) {
|
||||
// Gets cssRules output, ignoring CSSImportRules.
|
||||
if (cssRule.style) {
|
||||
// This is a fun little hack to get parentStyleSheet into the rule
|
||||
// object for IE since it failed to implement rule.parentStyleSheet.
|
||||
// We can later read this property when doing things like hunting
|
||||
// for indexes in order to delete a given CSSRule.
|
||||
// Unfortunately we have to use the style object to store these
|
||||
// pieces of info since the rule object is read-only.
|
||||
if (!cssRule.parentStyleSheet) {
|
||||
cssRule.style['-closure-parent-stylesheet'] = styleSheet;
|
||||
}
|
||||
|
||||
// This is a hack to help with possible removal of the rule later,
|
||||
// where we just append the rule's index in its parentStyleSheet
|
||||
// onto the style object as a property.
|
||||
// Unfortunately we have to use the style object to store these
|
||||
// pieces of info since the rule object is read-only.
|
||||
cssRule.style['-closure-rule-index'] = ruleIndex;
|
||||
}
|
||||
cssOut.push(cssRule);
|
||||
}
|
||||
|
||||
if (!isTextOutput) {
|
||||
ruleIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isTextOutput ? cssOut.join(' ') : cssOut;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,559 @@
|
||||
// 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
|
||||
* Central class for registering and accessing data sources
|
||||
* Also handles processing of data events.
|
||||
*
|
||||
* There is a shared global instance that most client code should access via
|
||||
* goog.ds.DataManager.getInstance(). However you can also create your own
|
||||
* DataManager using new
|
||||
*
|
||||
* Implements DataNode to provide the top element in a data registry
|
||||
* Prepends '$' to top level data names in path to denote they are root object
|
||||
*
|
||||
*/
|
||||
goog.provide('goog.ds.DataManager');
|
||||
|
||||
goog.require('goog.ds.BasicNodeList');
|
||||
goog.require('goog.ds.DataNode');
|
||||
goog.require('goog.ds.Expr');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.structs');
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a DataManger
|
||||
* @extends {goog.ds.DataNode}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.DataManager = function() {
|
||||
this.dataSources_ = new goog.ds.BasicNodeList();
|
||||
this.autoloads_ = new goog.structs.Map();
|
||||
this.listenerMap_ = {};
|
||||
this.listenersByFunction_ = {};
|
||||
this.aliases_ = {};
|
||||
this.eventCount_ = 0;
|
||||
this.indexedListenersByFunction_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global instance
|
||||
* @private
|
||||
*/
|
||||
goog.ds.DataManager.instance_ = null;
|
||||
goog.inherits(goog.ds.DataManager, goog.ds.DataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Get the global instance
|
||||
* @return {goog.ds.DataManager} The data manager singleton.
|
||||
*/
|
||||
goog.ds.DataManager.getInstance = function() {
|
||||
if (!goog.ds.DataManager.instance_) {
|
||||
goog.ds.DataManager.instance_ = new goog.ds.DataManager();
|
||||
}
|
||||
return goog.ds.DataManager.instance_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the global instance (for unit tests to reset state).
|
||||
*/
|
||||
goog.ds.DataManager.clearInstance = function() {
|
||||
goog.ds.DataManager.instance_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a data source
|
||||
* @param {goog.ds.DataNode} ds The data source.
|
||||
* @param {boolean=} opt_autoload Whether to automatically load the data,
|
||||
* defaults to false.
|
||||
* @param {string=} opt_name Optional name, can also get name
|
||||
* from the datasource.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.addDataSource = function(ds, opt_autoload,
|
||||
opt_name) {
|
||||
var autoload = !!opt_autoload;
|
||||
var name = opt_name || ds.getDataName();
|
||||
if (!goog.string.startsWith(name, '$')) {
|
||||
name = '$' + name;
|
||||
}
|
||||
ds.setDataName(name);
|
||||
this.dataSources_.add(ds);
|
||||
this.autoloads_.set(name, autoload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create an alias for a data path, very similar to assigning a variable.
|
||||
* For example, you can set $CurrentContact -> $Request/Contacts[5], and all
|
||||
* references to $CurrentContact will be procesed on $Request/Contacts[5].
|
||||
*
|
||||
* Aliases will hide datasources of the same name.
|
||||
*
|
||||
* @param {string} name Alias name, must be a top level path ($Foo).
|
||||
* @param {string} dataPath Data path being aliased.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.aliasDataSource = function(name, dataPath) {
|
||||
if (!this.aliasListener_) {
|
||||
this.aliasListener_ = goog.bind(this.listenForAlias_, this);
|
||||
}
|
||||
if (this.aliases_[name]) {
|
||||
var oldPath = this.aliases_[name].getSource();
|
||||
this.removeListeners(this.aliasListener_, oldPath + '/...', name);
|
||||
}
|
||||
this.aliases_[name] = goog.ds.Expr.create(dataPath);
|
||||
this.addListener(this.aliasListener_, dataPath + '/...', name);
|
||||
this.fireDataChange(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Listener function for matches of paths that have been aliased.
|
||||
* Fires a data change on the alias as well.
|
||||
*
|
||||
* @param {string} dataPath Path of data event fired.
|
||||
* @param {string} name Name of the alias.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.DataManager.prototype.listenForAlias_ = function(dataPath, name) {
|
||||
var aliasedExpr = this.aliases_[name];
|
||||
|
||||
if (aliasedExpr) {
|
||||
// If it's a subpath, appends the subpath to the alias name
|
||||
// otherwise just fires on the top level alias
|
||||
var aliasedPath = aliasedExpr.getSource();
|
||||
if (dataPath.indexOf(aliasedPath) == 0) {
|
||||
this.fireDataChange(name + dataPath.substring(aliasedPath.length));
|
||||
} else {
|
||||
this.fireDataChange(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node.
|
||||
*
|
||||
* @param {string} name The node name.
|
||||
* @return {goog.ds.DataNode} The child node,
|
||||
* or null if no node of this name exists.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getDataSource = function(name) {
|
||||
if (this.aliases_[name]) {
|
||||
return this.aliases_[name].getNode();
|
||||
} else {
|
||||
return this.dataSources_.get(name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the value of the node
|
||||
* @return {Object} The value of the node, or null if no value.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.get = function() {
|
||||
return this.dataSources_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.DataManager.prototype.set = function(value) {
|
||||
throw Error('Can\'t set on DataManager');
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.DataManager.prototype.getChildNodes = function(opt_selector) {
|
||||
if (opt_selector) {
|
||||
return new goog.ds.BasicNodeList(
|
||||
[this.getChildNode(/** @type {string} */(opt_selector))]);
|
||||
} else {
|
||||
return this.dataSources_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node
|
||||
* @param {string} name The node name.
|
||||
* @return {goog.ds.DataNode} The child node,
|
||||
* or null if no node of this name exists.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getChildNode = function(name) {
|
||||
return this.getDataSource(name);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.DataManager.prototype.getChildNodeValue = function(name) {
|
||||
var ds = this.getDataSource(name);
|
||||
return ds ? ds.get() : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getDataName = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the a qualified data path to this node
|
||||
* @return {string} The data path.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getDataPath = function() {
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node
|
||||
* only loads datasources flagged with autoload
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.load = function() {
|
||||
var len = this.dataSources_.getCount();
|
||||
for (var i = 0; i < len; i++) {
|
||||
var ds = this.dataSources_.getByIndex(i);
|
||||
var autoload = this.autoloads_.get(ds.getDataName());
|
||||
if (autoload) {
|
||||
ds.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getLoadState = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the value of this node is a homogeneous list of data
|
||||
* @return {boolean} True if a list.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataManager.prototype.isList = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the total count of events fired (mostly for debugging)
|
||||
* @return {number} Count of events.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getEventCount = function() {
|
||||
return this.eventCount_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener
|
||||
* Listeners should fire when any data with path that has dataPath as substring
|
||||
* is changed.
|
||||
* TODO(user) Look into better listener handling
|
||||
*
|
||||
* @param {Function} fn Callback function, signature function(dataPath, id).
|
||||
* @param {string} dataPath Fully qualified data path.
|
||||
* @param {string=} opt_id A value passed back to the listener when the dataPath
|
||||
* is matched.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.addListener = function(fn, dataPath, opt_id) {
|
||||
// maxAncestor sets how distant an ancestor you can be of the fired event
|
||||
// and still fire (you always fire if you are a descendant).
|
||||
// 0 means you don't fire if you are an ancestor
|
||||
// 1 means you only fire if you are parent
|
||||
// 1000 means you will fire if you are ancestor (effectively infinite)
|
||||
var maxAncestors = 0;
|
||||
if (goog.string.endsWith(dataPath, '/...')) {
|
||||
maxAncestors = 1000;
|
||||
dataPath = dataPath.substring(0, dataPath.length - 4);
|
||||
} else if (goog.string.endsWith(dataPath, '/*')) {
|
||||
maxAncestors = 1;
|
||||
dataPath = dataPath.substring(0, dataPath.length - 2);
|
||||
}
|
||||
|
||||
opt_id = opt_id || '';
|
||||
var key = dataPath + ':' + opt_id + ':' + goog.getUid(fn);
|
||||
var listener = {dataPath: dataPath, id: opt_id, fn: fn};
|
||||
var expr = goog.ds.Expr.create(dataPath);
|
||||
|
||||
var fnUid = goog.getUid(fn);
|
||||
if (!this.listenersByFunction_[fnUid]) {
|
||||
this.listenersByFunction_[fnUid] = {};
|
||||
}
|
||||
this.listenersByFunction_[fnUid][key] = {listener: listener, items: []};
|
||||
|
||||
while (expr) {
|
||||
var listenerSpec = {listener: listener, maxAncestors: maxAncestors};
|
||||
var matchingListeners = this.listenerMap_[expr.getSource()];
|
||||
if (matchingListeners == null) {
|
||||
matchingListeners = {};
|
||||
this.listenerMap_[expr.getSource()] = matchingListeners;
|
||||
}
|
||||
matchingListeners[key] = listenerSpec;
|
||||
maxAncestors = 0;
|
||||
expr = expr.getParent();
|
||||
this.listenersByFunction_[fnUid][key].items.push(
|
||||
{key: key, obj: matchingListeners});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an indexed listener.
|
||||
*
|
||||
* Indexed listeners allow for '*' in data paths. If a * exists, will match
|
||||
* all values and return the matched values in an array to the callback.
|
||||
*
|
||||
* Currently uses a promiscuous match algorithm: Matches everything before the
|
||||
* first '*', and then does a regex match for all of the returned events.
|
||||
* Although this isn't optimized, it is still an improvement as you can collapse
|
||||
* 100's of listeners into a single regex match
|
||||
*
|
||||
* @param {Function} fn Callback function, signature (dataPath, id, indexes).
|
||||
* @param {string} dataPath Fully qualified data path.
|
||||
* @param {string=} opt_id A value passed back to the listener when the dataPath
|
||||
* is matched.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.addIndexedListener = function(fn, dataPath,
|
||||
opt_id) {
|
||||
var firstStarPos = dataPath.indexOf('*');
|
||||
// Just need a regular listener
|
||||
if (firstStarPos == -1) {
|
||||
this.addListener(fn, dataPath, opt_id);
|
||||
return;
|
||||
}
|
||||
|
||||
var listenPath = dataPath.substring(0, firstStarPos) + '...';
|
||||
|
||||
// Create regex that matches * to any non '\' character
|
||||
var ext = '$';
|
||||
if (goog.string.endsWith(dataPath, '/...')) {
|
||||
dataPath = dataPath.substring(0, dataPath.length - 4);
|
||||
ext = '';
|
||||
}
|
||||
var regExpPath = goog.string.regExpEscape(dataPath);
|
||||
var matchRegExp = regExpPath.replace(/\\\*/g, '([^\\\/]+)') + ext;
|
||||
|
||||
// Matcher function applies the regex and calls back the original function
|
||||
// if the regex matches, passing in an array of the matched values
|
||||
var matchRegExpRe = new RegExp(matchRegExp);
|
||||
var matcher = function(path, id) {
|
||||
var match = matchRegExpRe.exec(path);
|
||||
if (match) {
|
||||
match.shift();
|
||||
fn(path, opt_id, match);
|
||||
}
|
||||
}
|
||||
this.addListener(matcher, listenPath, opt_id);
|
||||
|
||||
// Add the indexed listener to the map so that we can remove it later.
|
||||
var fnUid = goog.getUid(fn);
|
||||
if (!this.indexedListenersByFunction_[fnUid]) {
|
||||
this.indexedListenersByFunction_[fnUid] = {};
|
||||
}
|
||||
var key = dataPath + ':' + opt_id;
|
||||
this.indexedListenersByFunction_[fnUid][key] = {
|
||||
listener: {dataPath: listenPath, fn: matcher, id: opt_id}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes indexed listeners with a given callback function, and optional
|
||||
* matching datapath and matching id.
|
||||
*
|
||||
* @param {Function} fn Callback function, signature function(dataPath, id).
|
||||
* @param {string=} opt_dataPath Fully qualified data path.
|
||||
* @param {string=} opt_id A value passed back to the listener when the dataPath
|
||||
* is matched.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.removeIndexedListeners = function(
|
||||
fn, opt_dataPath, opt_id) {
|
||||
this.removeListenersByFunction_(
|
||||
this.indexedListenersByFunction_, true, fn, opt_dataPath, opt_id);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes listeners with a given callback function, and optional
|
||||
* matching dataPath and matching id
|
||||
*
|
||||
* @param {Function} fn Callback function, signature function(dataPath, id).
|
||||
* @param {string=} opt_dataPath Fully qualified data path.
|
||||
* @param {string=} opt_id A value passed back to the listener when the dataPath
|
||||
* is matched.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.removeListeners = function(fn, opt_dataPath,
|
||||
opt_id) {
|
||||
|
||||
// Normalize data path root
|
||||
if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/...')) {
|
||||
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 4);
|
||||
} else if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/*')) {
|
||||
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 2);
|
||||
}
|
||||
|
||||
this.removeListenersByFunction_(
|
||||
this.listenersByFunction_, false, fn, opt_dataPath, opt_id);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes listeners with a given callback function, and optional
|
||||
* matching dataPath and matching id from the given listenersByFunction
|
||||
* data structure.
|
||||
*
|
||||
* @param {Object} listenersByFunction The listeners by function.
|
||||
* @param {boolean} indexed Indicates whether the listenersByFunction are
|
||||
* indexed or not.
|
||||
* @param {Function} fn Callback function, signature function(dataPath, id).
|
||||
* @param {string=} opt_dataPath Fully qualified data path.
|
||||
* @param {string=} opt_id A value passed back to the listener when the dataPath
|
||||
* is matched.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.DataManager.prototype.removeListenersByFunction_ = function(
|
||||
listenersByFunction, indexed, fn, opt_dataPath, opt_id) {
|
||||
var fnUid = goog.getUid(fn);
|
||||
var functionMatches = listenersByFunction[fnUid];
|
||||
if (functionMatches != null) {
|
||||
for (var key in functionMatches) {
|
||||
var functionMatch = functionMatches[key];
|
||||
var listener = functionMatch.listener;
|
||||
if ((!opt_dataPath || opt_dataPath == listener.dataPath) &&
|
||||
(!opt_id || opt_id == listener.id)) {
|
||||
if (indexed) {
|
||||
this.removeListeners(
|
||||
listener.fn, listener.dataPath, listener.id);
|
||||
}
|
||||
if (functionMatch.items) {
|
||||
for (var i = 0; i < functionMatch.items.length; i++) {
|
||||
var item = functionMatch.items[i];
|
||||
delete item.obj[item.key];
|
||||
}
|
||||
}
|
||||
delete functionMatches[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the total number of listeners (per expression listened to, so may be
|
||||
* more than number of times addListener() has been called
|
||||
* @return {number} Number of listeners.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.getListenerCount = function() {
|
||||
var count = 0;
|
||||
goog.structs.forEach(this.listenerMap_, function(matchingListeners) {
|
||||
count += goog.structs.getCount(matchingListeners);
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disables the sending of all data events during the execution of the given
|
||||
* callback. This provides a way to avoid useless notifications of small changes
|
||||
* when you will eventually send a data event manually that encompasses them
|
||||
* all.
|
||||
*
|
||||
* Note that this function can not be called reentrantly.
|
||||
*
|
||||
* @param {Function} callback Zero-arg function to execute.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.runWithoutFiringDataChanges = function(callback) {
|
||||
if (this.disableFiring_) {
|
||||
throw Error('Can not nest calls to runWithoutFiringDataChanges');
|
||||
}
|
||||
|
||||
this.disableFiring_ = true;
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
this.disableFiring_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fire a data change event to all listeners
|
||||
*
|
||||
* If the path matches the path of a listener, the listener will fire
|
||||
*
|
||||
* If your path is the parent of a listener, the listener will fire. I.e.
|
||||
* if $Contacts/bob@bob.com changes, then we will fire listener for
|
||||
* $Contacts/bob@bob.com/Name as well, as the assumption is that when
|
||||
* a parent changes, all children are invalidated.
|
||||
*
|
||||
* If your path is the child of a listener, the listener may fire, depending
|
||||
* on the ancestor depth.
|
||||
*
|
||||
* A listener for $Contacts might only be interested if the contact name changes
|
||||
* (i.e. $Contacts doesn't fire on $Contacts/bob@bob.com/Name),
|
||||
* while a listener for a specific contact might
|
||||
* (i.e. $Contacts/bob@bob.com would fire on $Contacts/bob@bob.com/Name).
|
||||
* Adding "/..." to a lisetener path listens to all children, and adding "/*" to
|
||||
* a listener path listens only to direct children
|
||||
*
|
||||
* @param {string} dataPath Fully qualified data path.
|
||||
*/
|
||||
goog.ds.DataManager.prototype.fireDataChange = function(dataPath) {
|
||||
if (this.disableFiring_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var expr = goog.ds.Expr.create(dataPath);
|
||||
var ancestorDepth = 0;
|
||||
|
||||
// Look for listeners for expression and all its parents.
|
||||
// Parents of listener expressions are all added to the listenerMap as well,
|
||||
// so this will evaluate inner loop every time the dataPath is a child or
|
||||
// an ancestor of the original listener path
|
||||
while (expr) {
|
||||
var matchingListeners = this.listenerMap_[expr.getSource()];
|
||||
if (matchingListeners) {
|
||||
for (var id in matchingListeners) {
|
||||
var match = matchingListeners[id];
|
||||
var listener = match.listener;
|
||||
if (ancestorDepth <= match.maxAncestors) {
|
||||
listener.fn(dataPath, listener.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
ancestorDepth++;
|
||||
expr = expr.getParent();
|
||||
}
|
||||
this.eventCount_++;
|
||||
};
|
||||
@@ -0,0 +1,656 @@
|
||||
// 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 Generic rich data access API.
|
||||
*
|
||||
* Abstraction for data sources that allows listening for changes at different
|
||||
* levels of the data tree and updating the data via XHR requests
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ds.BaseDataNode');
|
||||
goog.provide('goog.ds.BasicNodeList');
|
||||
goog.provide('goog.ds.DataNode');
|
||||
goog.provide('goog.ds.DataNodeList');
|
||||
goog.provide('goog.ds.EmptyNodeList');
|
||||
goog.provide('goog.ds.LoadState');
|
||||
goog.provide('goog.ds.SortedNodeList');
|
||||
goog.provide('goog.ds.Util');
|
||||
goog.provide('goog.ds.logger');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug.Logger');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interface for node in rich data tree.
|
||||
*
|
||||
* Names that are reserved for system use and shouldn't be used for data node
|
||||
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
|
||||
* undefined if these names are used.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.DataNode = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Get the value of the node
|
||||
* @param {...?} var_args Do not check arity of arguments, because
|
||||
* some subclasses require args.
|
||||
* @return {*} The value of the node, or null if no value.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.get = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of the node
|
||||
* @param {*} value The new value of the node.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.set = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets all of the child nodes of the current node.
|
||||
* Should return an empty DataNode list if no child nodes.
|
||||
* @param {string=} opt_selector String selector to choose child nodes.
|
||||
* @return {goog.ds.DataNodeList} The child nodes.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getChildNodes = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node
|
||||
* @param {string} name The node name.
|
||||
* @param {boolean=} opt_canCreate Whether to create a child node if it does not
|
||||
* exist.
|
||||
* @return {goog.ds.DataNode} The child node, or null
|
||||
* if no node of this name exists.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getChildNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a child node
|
||||
* @param {string} name The node name.
|
||||
* @return {*} The value of the node, or null if no value or the child node
|
||||
* doesn't exist.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getChildNodeValue = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sets a named child node of the current node.
|
||||
*
|
||||
* @param {string} name The node name.
|
||||
* @param {Object} value The value to set, can be DataNode, object, property,
|
||||
* or null. If value is null, removes the child node.
|
||||
* @return {Object} The child node, if the node was set.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.setChildNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getDataName = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Set the name of the node relative to the parent node
|
||||
* @param {string} name The name of the node.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.setDataName = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the a qualified data path to this node
|
||||
* @return {string} The data path.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getDataPath = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node
|
||||
*/
|
||||
goog.ds.DataNode.prototype.load = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.getLoadState = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the value of this node is a homogeneous list of data
|
||||
* @return {boolean} True if a list.
|
||||
*/
|
||||
goog.ds.DataNode.prototype.isList = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Enum for load state of a DataNode.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.ds.LoadState = {
|
||||
LOADED: 'LOADED',
|
||||
LOADING: 'LOADING',
|
||||
FAILED: 'FAILED',
|
||||
NOT_LOADED: 'NOT_LOADED'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for data node functionality, has default implementations for
|
||||
* many of the functions.
|
||||
*
|
||||
* implements {goog.ds.DataNode}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.BaseDataNode = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of the node
|
||||
* @param {Object} value The new value of the node.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.set = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets all of the child nodes of the current node.
|
||||
* Should return an empty DataNode list if no child nodes.
|
||||
* @param {string=} opt_selector String selector to choose child nodes.
|
||||
* @return {goog.ds.DataNodeList} The child nodes.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getChildNodes = function(opt_selector) {
|
||||
return new goog.ds.EmptyNodeList();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node
|
||||
* @param {string} name The node name.
|
||||
* @param {boolean=} opt_canCreate Whether you can create the child node if
|
||||
* it doesn't exist already.
|
||||
* @return {goog.ds.DataNode} The child node, or null if no node of
|
||||
* this name exists and opt_create is false.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getChildNode = function(name, opt_canCreate) {
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a child node
|
||||
* @param {string} name The node name.
|
||||
* @return {Object} The value of the node, or null if no value or the
|
||||
* child node doesn't exist.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getChildNodeValue = function(name) {
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getDataName = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the a qualified data path to this node
|
||||
* @return {string} The data path.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getDataPath = function() {
|
||||
var parentPath = '';
|
||||
var myName = this.getDataName();
|
||||
if (this.getParent && this.getParent()) {
|
||||
parentPath = this.getParent().getDataPath() +
|
||||
(myName.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
|
||||
goog.ds.STR_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
return parentPath + myName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.load = goog.nullFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getLoadState = function() {
|
||||
return goog.ds.LoadState.LOADED;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the parent node. Subclasses implement this function
|
||||
* @type {Function}
|
||||
* @protected
|
||||
*/
|
||||
goog.ds.BaseDataNode.prototype.getParent = null;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for node list in rich data tree.
|
||||
*
|
||||
* Has both map and list-style accessors
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.ds.DataNode}
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available.
|
||||
goog.ds.DataNodeList = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Add a node to the node list.
|
||||
* If the node has a dataName, uses this for the key in the map.
|
||||
*
|
||||
* @param {goog.ds.DataNode} node The node to add.
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.add = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Get a node by string key.
|
||||
* Returns null if node doesn't exist.
|
||||
*
|
||||
* @param {string} key String lookup key.
|
||||
* @return {*} The node, or null if doesn't exist.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.get = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Get a node by index
|
||||
* Returns null if the index is out of range
|
||||
*
|
||||
* @param {number} index The index of the node.
|
||||
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.getByIndex = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the size of the node list
|
||||
*
|
||||
* @return {number} The size of the list.
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.getCount = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sets a node in the list of a given name
|
||||
* @param {string} name Name of the node.
|
||||
* @param {goog.ds.DataNode} node The node.
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.setNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Removes a node in the list of a given name
|
||||
* @param {string} name Name of the node.
|
||||
* @return {boolean} True if node existed and was deleted.
|
||||
*/
|
||||
goog.ds.DataNodeList.prototype.removeNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Simple node list implementation with underlying array and map
|
||||
* implements goog.ds.DataNodeList.
|
||||
*
|
||||
* Names that are reserved for system use and shouldn't be used for data node
|
||||
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
|
||||
* undefined if these names are used.
|
||||
*
|
||||
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list.
|
||||
* @constructor
|
||||
* @extends {goog.ds.DataNodeList}
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available.
|
||||
goog.ds.BasicNodeList = function(opt_nodes) {
|
||||
this.map_ = {};
|
||||
this.list_ = [];
|
||||
this.indexMap_ = {};
|
||||
if (opt_nodes) {
|
||||
for (var i = 0, node; node = opt_nodes[i]; i++) {
|
||||
this.add(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a node to the node list.
|
||||
* If the node has a dataName, uses this for the key in the map.
|
||||
* TODO(user) Remove function as well
|
||||
*
|
||||
* @param {goog.ds.DataNode} node The node to add.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.add = function(node) {
|
||||
this.list_.push(node);
|
||||
var dataName = node.getDataName();
|
||||
if (dataName) {
|
||||
this.map_[dataName] = node;
|
||||
this.indexMap_[dataName] = this.list_.length - 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a node by string key.
|
||||
* Returns null if node doesn't exist.
|
||||
*
|
||||
* @param {string} key String lookup key.
|
||||
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.get = function(key) {
|
||||
return this.map_[key] || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a node by index
|
||||
* Returns null if the index is out of range
|
||||
*
|
||||
* @param {number} index The index of the node.
|
||||
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.getByIndex = function(index) {
|
||||
return this.list_[index] || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the size of the node list
|
||||
*
|
||||
* @return {number} The size of the list.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.getCount = function() {
|
||||
return this.list_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a node in the list of a given name
|
||||
* @param {string} name Name of the node.
|
||||
* @param {goog.ds.DataNode} node The node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.setNode = function(name, node) {
|
||||
if (node == null) {
|
||||
this.removeNode(name);
|
||||
} else {
|
||||
var existingNode = this.indexMap_[name];
|
||||
if (existingNode != null) {
|
||||
this.map_[name] = node;
|
||||
this.list_[existingNode] = node;
|
||||
} else {
|
||||
this.add(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a node in the list of a given name
|
||||
* @param {string} name Name of the node.
|
||||
* @return {boolean} True if node existed and was deleted.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.removeNode = function(name) {
|
||||
var existingNode = this.indexMap_[name];
|
||||
if (existingNode != null) {
|
||||
this.list_.splice(existingNode, 1);
|
||||
delete this.map_[name];
|
||||
delete this.indexMap_[name];
|
||||
for (var index in this.indexMap_) {
|
||||
if (this.indexMap_[index] > existingNode) {
|
||||
this.indexMap_[index]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return existingNode != null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the index of a named node
|
||||
* @param {string} name The name of the node to get the index of.
|
||||
* @return {number|undefined} The index.
|
||||
*/
|
||||
goog.ds.BasicNodeList.prototype.indexOf = function(name) {
|
||||
return this.indexMap_[name];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Immulatable empty node list
|
||||
* @extends {goog.ds.BasicNodeList}
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
goog.ds.EmptyNodeList = function() {
|
||||
goog.ds.BasicNodeList.call(this);
|
||||
};
|
||||
goog.inherits(goog.ds.EmptyNodeList, goog.ds.BasicNodeList);
|
||||
|
||||
|
||||
/**
|
||||
* Add a node to the node list.
|
||||
* If the node has a dataName, uses this for the key in the map.
|
||||
*
|
||||
* @param {goog.ds.DataNode} node The node to add.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.EmptyNodeList.prototype.add = function(node) {
|
||||
throw Error('Can\'t add to EmptyNodeList');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Node list implementation which maintains sort order during insertion and
|
||||
* modification operations based on a comparison function.
|
||||
*
|
||||
* The SortedNodeList does not guarantee sort order will be maintained if
|
||||
* the underlying data nodes are modified externally.
|
||||
*
|
||||
* Names that are reserved for system use and shouldn't be used for data node
|
||||
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
|
||||
* undefined if these names are used.
|
||||
*
|
||||
* @param {Function} compareFn Comparison function by which the
|
||||
* node list is sorted. Should take 2 arguments to compare, and return a
|
||||
* negative integer, zero, or a positive integer depending on whether the
|
||||
* first argument is less than, equal to, or greater than the second.
|
||||
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list;
|
||||
* these are assumed to be in sorted order.
|
||||
* @extends {goog.ds.BasicNodeList}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.SortedNodeList = function(compareFn, opt_nodes) {
|
||||
this.compareFn_ = compareFn;
|
||||
goog.ds.BasicNodeList.call(this, opt_nodes);
|
||||
};
|
||||
goog.inherits(goog.ds.SortedNodeList, goog.ds.BasicNodeList);
|
||||
|
||||
|
||||
/**
|
||||
* Add a node to the node list, maintaining sort order.
|
||||
* If the node has a dataName, uses this for the key in the map.
|
||||
*
|
||||
* @param {goog.ds.DataNode} node The node to add.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.SortedNodeList.prototype.add = function(node) {
|
||||
if (!this.compareFn_) {
|
||||
this.append(node);
|
||||
return;
|
||||
}
|
||||
|
||||
var searchLoc = goog.array.binarySearch(this.list_, node, this.compareFn_);
|
||||
|
||||
// if there is another node that is "equal" according to the comparison
|
||||
// function, insert before that one; otherwise insert at the location
|
||||
// goog.array.binarySearch indicated
|
||||
if (searchLoc < 0) {
|
||||
searchLoc = -(searchLoc + 1);
|
||||
}
|
||||
|
||||
// update any indexes that are after the insertion point
|
||||
for (var index in this.indexMap_) {
|
||||
if (this.indexMap_[index] >= searchLoc) {
|
||||
this.indexMap_[index]++;
|
||||
}
|
||||
}
|
||||
|
||||
goog.array.insertAt(this.list_, node, searchLoc);
|
||||
var dataName = node.getDataName();
|
||||
if (dataName) {
|
||||
this.map_[dataName] = node;
|
||||
this.indexMap_[dataName] = searchLoc;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the given node to the end of the SortedNodeList. This should
|
||||
* only be used when the caller can guarantee that the sort order will
|
||||
* be maintained according to this SortedNodeList's compareFn (e.g.
|
||||
* when initializing a new SortedNodeList from a list of nodes that has
|
||||
* already been sorted).
|
||||
* @param {goog.ds.DataNode} node The node to append.
|
||||
*/
|
||||
goog.ds.SortedNodeList.prototype.append = function(node) {
|
||||
goog.ds.SortedNodeList.superClass_.add.call(this, node);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a node in the list of a given name, maintaining sort order.
|
||||
* @param {string} name Name of the node.
|
||||
* @param {goog.ds.DataNode} node The node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.SortedNodeList.prototype.setNode = function(name, node) {
|
||||
if (node == null) {
|
||||
this.removeNode(name);
|
||||
} else {
|
||||
var existingNode = this.indexMap_[name];
|
||||
if (existingNode != null) {
|
||||
if (this.compareFn_) {
|
||||
var compareResult = this.compareFn_(this.list_[existingNode], node);
|
||||
if (compareResult == 0) {
|
||||
// the new node can just replace the old one
|
||||
this.map_[name] = node;
|
||||
this.list_[existingNode] = node;
|
||||
} else {
|
||||
// remove the old node, then add the new one
|
||||
this.removeNode(name);
|
||||
this.add(node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.add(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The character denoting an attribute.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ds.STR_ATTRIBUTE_START = '@';
|
||||
|
||||
|
||||
/**
|
||||
* The character denoting all children.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ds.STR_ALL_CHILDREN_SELECTOR = '*';
|
||||
|
||||
|
||||
/**
|
||||
* The wildcard character.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ds.STR_WILDCARD = '*';
|
||||
|
||||
|
||||
/**
|
||||
* The character denoting path separation.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ds.STR_PATH_SEPARATOR = '/';
|
||||
|
||||
|
||||
/**
|
||||
* The character denoting the start of an array.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ds.STR_ARRAY_START = '[';
|
||||
|
||||
|
||||
/**
|
||||
* Shared logger instance for data package
|
||||
* @type {goog.debug.Logger}
|
||||
*/
|
||||
goog.ds.logger = goog.debug.Logger.getLogger('goog.ds');
|
||||
|
||||
|
||||
/**
|
||||
* Create a data node that references another data node,
|
||||
* useful for pointer-like functionality.
|
||||
* All functions will return same values as the original node except for
|
||||
* getDataName()
|
||||
* @param {!goog.ds.DataNode} node The original node.
|
||||
* @param {string} name The new name.
|
||||
* @return {!goog.ds.DataNode} The new data node.
|
||||
*/
|
||||
goog.ds.Util.makeReferenceNode = function(node, name) {
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {goog.ds.DataNode}
|
||||
*/
|
||||
var nodeCreator = function() {};
|
||||
nodeCreator.prototype = node;
|
||||
var newNode = new nodeCreator();
|
||||
newNode.getDataName = function() {
|
||||
return name;
|
||||
};
|
||||
return newNode;
|
||||
};
|
||||
@@ -0,0 +1,544 @@
|
||||
// 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
|
||||
* Expression evaluation utilities. Expression format is very similar to XPath.
|
||||
*
|
||||
* Expression details:
|
||||
* - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B').
|
||||
* getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on
|
||||
* call
|
||||
* - If expression ends with '/name()', will get the name() of the node
|
||||
* referenced by the preceding path.
|
||||
* - If expression ends with '/count()', will get the count() of the nodes that
|
||||
* match the expression referenced by the preceding path.
|
||||
* - If expression ends with '?', the value is OK to evaluate to null. This is
|
||||
* not enforced by the expression evaluation functions, instead it is
|
||||
* provided as a flag for client code which may ignore depending on usage
|
||||
* - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX)
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ds.Expr');
|
||||
|
||||
goog.require('goog.ds.BasicNodeList');
|
||||
goog.require('goog.ds.EmptyNodeList');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a new expression. An expression uses a string expression language, and
|
||||
* from this string and a passed in DataNode can evaluate to a value, DataNode,
|
||||
* or a DataNodeList.
|
||||
*
|
||||
* @param {string=} opt_expr The string expression.
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.Expr = function(opt_expr) {
|
||||
if (opt_expr) {
|
||||
this.setSource_(opt_expr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the source expression text & parse
|
||||
*
|
||||
* @param {string} expr The string expression source.
|
||||
* @param {Array=} opt_parts Array of the parts of an expression.
|
||||
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
|
||||
* passed in as a hint for processing.
|
||||
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
|
||||
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
|
||||
* processing.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.setSource_ = function(expr, opt_parts,
|
||||
opt_childExpr, opt_prevExpr) {
|
||||
this.src_ = expr;
|
||||
|
||||
if (!opt_childExpr && !opt_prevExpr) {
|
||||
// Check whether it can be empty
|
||||
if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) {
|
||||
this.canBeEmpty_ = true;
|
||||
expr = expr.substring(0, expr.length - 1);
|
||||
}
|
||||
|
||||
// Check whether this is an node function
|
||||
if (goog.string.endsWith(expr, '()')) {
|
||||
if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) ||
|
||||
goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) ||
|
||||
goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) {
|
||||
var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR);
|
||||
if (lastPos != -1) {
|
||||
this.exprFn_ = expr.substring(lastPos + 1);
|
||||
expr = expr.substring(0, lastPos);
|
||||
} else {
|
||||
this.exprFn_ = expr;
|
||||
expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR;
|
||||
}
|
||||
if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) {
|
||||
this.isCount_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Split into component parts
|
||||
this.parts_ = opt_parts || expr.split('/');
|
||||
this.size_ = this.parts_.length;
|
||||
this.last_ = this.parts_[this.size_ - 1];
|
||||
this.root_ = this.parts_[0];
|
||||
|
||||
if (this.size_ == 1) {
|
||||
this.rootExpr_ = this;
|
||||
this.isAbsolute_ = goog.string.startsWith(expr, '$');
|
||||
} else {
|
||||
this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null,
|
||||
this, null);
|
||||
this.isAbsolute_ = this.rootExpr_.isAbsolute_;
|
||||
this.root_ = this.rootExpr_.root_;
|
||||
}
|
||||
|
||||
if (this.size_ == 1 && !this.isAbsolute_) {
|
||||
// Check whether expression maps to current node, for convenience
|
||||
this.isCurrent_ = (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR ||
|
||||
expr == goog.ds.Expr.String_.EMPTY_EXPR);
|
||||
|
||||
// Whether this expression is just an attribute (i.e. '@foo')
|
||||
this.isJustAttribute_ =
|
||||
goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START);
|
||||
|
||||
// Check whether this is a common node expression
|
||||
this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR;
|
||||
this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR;
|
||||
this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the source data path for the expression
|
||||
* @return {string} The path.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getSource = function() {
|
||||
return this.src_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the last part of the expression.
|
||||
* @return {?string} Last part of the expression.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getLast = function() {
|
||||
return this.last_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the parent expression of this expression, or null if this is top level
|
||||
* @return {goog.ds.Expr} The parent.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getParent = function() {
|
||||
if (!this.parentExprSet_) {
|
||||
if (this.size_ > 1) {
|
||||
this.parentExpr_ = goog.ds.Expr.createInternal_(null,
|
||||
this.parts_.slice(0, this.parts_.length - 1), this, null);
|
||||
}
|
||||
this.parentExprSet_ = true;
|
||||
}
|
||||
return this.parentExpr_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the parent expression of this expression, or null if this is top level
|
||||
* @return {goog.ds.Expr} The parent.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getNext = function() {
|
||||
if (!this.nextExprSet_) {
|
||||
if (this.size_ > 1) {
|
||||
this.nextExpr_ = goog.ds.Expr.createInternal_(null, this.parts_.slice(1),
|
||||
null, this);
|
||||
}
|
||||
this.nextExprSet_ = true;
|
||||
}
|
||||
return this.nextExpr_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate an expression on a data node, and return a value
|
||||
* Recursively walks through child nodes to evaluate
|
||||
* TODO(user) Support other expression functions
|
||||
*
|
||||
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
|
||||
* If not provided, evaluates against DataManager global root.
|
||||
* @return {*} Value of the node, or null if doesn't exist.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getValue = function(opt_ds) {
|
||||
if (opt_ds == null) {
|
||||
opt_ds = goog.ds.DataManager.getInstance();
|
||||
} else if (this.isAbsolute_) {
|
||||
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
|
||||
goog.ds.DataManager.getInstance();
|
||||
}
|
||||
|
||||
if (this.isCount_) {
|
||||
var nodes = this.getNodes(opt_ds);
|
||||
return nodes.getCount();
|
||||
}
|
||||
|
||||
if (this.size_ == 1) {
|
||||
return opt_ds.getChildNodeValue(this.root_);
|
||||
} else if (this.size_ == 0) {
|
||||
return opt_ds.get();
|
||||
}
|
||||
|
||||
var nextDs = opt_ds.getChildNode(this.root_);
|
||||
|
||||
if (nextDs == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.getNext().getValue(nextDs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate an expression on a data node, and return matching nodes
|
||||
* Recursively walks through child nodes to evaluate
|
||||
*
|
||||
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
|
||||
* If not provided, evaluates against data root.
|
||||
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
|
||||
* @return {goog.ds.DataNodeList} Matching nodes.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) {
|
||||
return /** @type {goog.ds.DataNodeList} */(this.getNodes_(opt_ds,
|
||||
false, opt_canCreate));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate an expression on a data node, and return the first matching node
|
||||
* Recursively walks through child nodes to evaluate
|
||||
*
|
||||
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
|
||||
* If not provided, evaluates against DataManager global root.
|
||||
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
|
||||
* @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist.
|
||||
*/
|
||||
goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) {
|
||||
return /** @type {goog.ds.DataNode} */(this.getNodes_(opt_ds,
|
||||
true, opt_canCreate));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate an expression on a data node, and return the first matching node
|
||||
* Recursively walks through child nodes to evaluate
|
||||
*
|
||||
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
|
||||
* If not provided, evaluates against DataManager global root.
|
||||
* @param {boolean=} opt_selectOne Whether to return single matching DataNode
|
||||
* or matching nodes in DataNodeList.
|
||||
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
|
||||
* @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes,
|
||||
* depending on value of opt_selectOne.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.getNodes_ = function(opt_ds, opt_selectOne,
|
||||
opt_canCreate) {
|
||||
if (opt_ds == null) {
|
||||
opt_ds = goog.ds.DataManager.getInstance();
|
||||
} else if (this.isAbsolute_) {
|
||||
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
|
||||
goog.ds.DataManager.getInstance();
|
||||
}
|
||||
|
||||
if (this.size_ == 0 && opt_selectOne) {
|
||||
return opt_ds;
|
||||
} else if (this.size_ == 0 && !opt_selectOne) {
|
||||
return new goog.ds.BasicNodeList([opt_ds]);
|
||||
} else if (this.size_ == 1) {
|
||||
if (opt_selectOne) {
|
||||
return opt_ds.getChildNode(this.root_, opt_canCreate);
|
||||
}
|
||||
else {
|
||||
var possibleListChild = opt_ds.getChildNode(this.root_);
|
||||
if (possibleListChild && possibleListChild.isList()) {
|
||||
return possibleListChild.getChildNodes();
|
||||
} else {
|
||||
return opt_ds.getChildNodes(this.root_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate);
|
||||
if (nextDs == null && opt_selectOne) {
|
||||
return null;
|
||||
} else if (nextDs == null && !opt_selectOne) {
|
||||
return new goog.ds.EmptyNodeList();
|
||||
}
|
||||
return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the expression can be null.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.canBeEmpty_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The parsed paths in the expression
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.parts_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Number of paths in the expression
|
||||
*
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.size_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The root node path in the expression
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.root_;
|
||||
|
||||
|
||||
/**
|
||||
* The last path in the expression
|
||||
*
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.last_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the expression evaluates to current node
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.isCurrent_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the expression is just an attribute
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.isJustAttribute_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Does this expression select all DOM-style child nodes (element and text)
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.isAllChildNodes_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Does this expression select all DOM-style attribute nodes (starts with '@')
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.isAllAttributes_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Does this expression select all DOM-style element child nodes
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.isAllElements_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The function used by this expression
|
||||
*
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.exprFn_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Cached value for the parent expression.
|
||||
* @type {goog.ds.Expr?}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.parentExpr_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Cached value for the next expression.
|
||||
* @type {goog.ds.Expr?}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.prototype.nextExpr_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create an expression from a string, can use cached values
|
||||
*
|
||||
* @param {string} expr The expression string.
|
||||
* @return {goog.ds.Expr} The expression object.
|
||||
*/
|
||||
goog.ds.Expr.create = function(expr) {
|
||||
var result = goog.ds.Expr.cache_[expr];
|
||||
|
||||
if (result == null) {
|
||||
result = new goog.ds.Expr(expr);
|
||||
goog.ds.Expr.cache_[expr] = result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create an expression from a string, can use cached values
|
||||
* Uses hints from related expressions to help in creation
|
||||
*
|
||||
* @param {?string=} opt_expr The string expression source.
|
||||
* @param {Array=} opt_parts Array of the parts of an expression.
|
||||
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
|
||||
* passed in as a hint for processing.
|
||||
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
|
||||
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
|
||||
* processing.
|
||||
* @return {goog.ds.Expr} The expression object.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.createInternal_ = function(opt_expr, opt_parts, opt_childExpr,
|
||||
opt_prevExpr) {
|
||||
var expr = opt_expr || opt_parts.join('/');
|
||||
var result = goog.ds.Expr.cache_[expr];
|
||||
|
||||
if (result == null) {
|
||||
result = new goog.ds.Expr();
|
||||
result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr);
|
||||
goog.ds.Expr.cache_[expr] = result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cache of pre-parsed expressions
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.cache_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Commonly used strings in expressions.
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.Expr.String_ = {
|
||||
SEPARATOR: '/',
|
||||
CURRENT_NODE_EXPR: '.',
|
||||
EMPTY_EXPR: '',
|
||||
ATTRIBUTE_START: '@',
|
||||
ALL_CHILD_NODES_EXPR: '*|text()',
|
||||
ALL_ATTRIBUTES_EXPR: '@*',
|
||||
ALL_ELEMENTS_EXPR: '*',
|
||||
NAME_EXPR: 'name()',
|
||||
COUNT_EXPR: 'count()',
|
||||
POSITION_EXPR: 'position()',
|
||||
INDEX_START: '[',
|
||||
INDEX_END: ']',
|
||||
CAN_BE_EMPTY: '?'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Standard expressions
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* The current node
|
||||
*/
|
||||
goog.ds.Expr.CURRENT = goog.ds.Expr.create(
|
||||
goog.ds.Expr.String_.CURRENT_NODE_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* For DOM interop - all DOM child nodes (text + element).
|
||||
* Text nodes have dataName #text
|
||||
*/
|
||||
goog.ds.Expr.ALL_CHILD_NODES =
|
||||
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* For DOM interop - all DOM element child nodes
|
||||
*/
|
||||
goog.ds.Expr.ALL_ELEMENTS =
|
||||
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* For DOM interop - all DOM attribute nodes
|
||||
* Attribute nodes have dataName starting with "@"
|
||||
*/
|
||||
goog.ds.Expr.ALL_ATTRIBUTES =
|
||||
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* Get the dataName of a node
|
||||
*/
|
||||
goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* Get the count of nodes matching an expression
|
||||
*/
|
||||
goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR);
|
||||
|
||||
|
||||
/**
|
||||
* Get the position of the "current" node in the current node list
|
||||
* This will only apply for datasources that support the concept of a current
|
||||
* node (none exist yet). This is similar to XPath position() and concept of
|
||||
* current node
|
||||
*/
|
||||
goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);
|
||||
@@ -0,0 +1,812 @@
|
||||
// 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
|
||||
* Efficient implementation of DataNode API.
|
||||
*
|
||||
* The implementation consists of three concrete classes for modelling
|
||||
* DataNodes with different characteristics: FastDataNode,
|
||||
* FastPrimitiveDataNode and FastListNode.
|
||||
*
|
||||
* FastDataNode is for bean-like or map-like objects that consists of
|
||||
* key/value mappings and where the primary access pattern is by key.
|
||||
*
|
||||
* FastPrimitiveDataNode wraps primitives like strings, boolean, and numbers.
|
||||
*
|
||||
* FastListNode is for array-like data nodes. It also supports key-based
|
||||
* lookups if the data nodes have an "id" property or if child nodes are
|
||||
* explicitly added by name. It is most efficient if these features are not
|
||||
* used.
|
||||
*
|
||||
* FastDataNodes can be constructed from JSON-like objects via the function
|
||||
* goog.ds.FastDataNode.fromJs.
|
||||
|
||||
*/
|
||||
|
||||
goog.provide('goog.ds.AbstractFastDataNode');
|
||||
goog.provide('goog.ds.FastDataNode');
|
||||
goog.provide('goog.ds.FastListNode');
|
||||
goog.provide('goog.ds.PrimitiveFastDataNode');
|
||||
|
||||
goog.require('goog.ds.DataManager');
|
||||
goog.require('goog.ds.EmptyNodeList');
|
||||
goog.require('goog.string');
|
||||
|
||||
/*
|
||||
* Implementation note: In order to reduce the number of objects,
|
||||
* FastDataNode stores its key/value mappings directly in the FastDataNode
|
||||
* object iself (instead of a separate map). To make this work we have to
|
||||
* sure that there are no name clashes with other attribute names used by
|
||||
* FastDataNode (like dataName and parent). This is especially difficult in
|
||||
* the light of automatic renaming by the JavaScript compiler. For this reason,
|
||||
* all internal attributes start with "__" so that they are not renamed
|
||||
* by the compiler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new abstract data node.
|
||||
* @param {string} dataName Name of the datanode.
|
||||
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
|
||||
* @constructor
|
||||
* @extends {goog.ds.DataNodeList}
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available.
|
||||
goog.ds.AbstractFastDataNode = function(dataName, opt_parent) {
|
||||
if (!dataName) {
|
||||
throw Error('Cannot create a fast data node without a data name');
|
||||
}
|
||||
this['__dataName'] = dataName;
|
||||
this['__parent'] = opt_parent;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of this data node.
|
||||
* @return {string} Name of this data noden.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.AbstractFastDataNode.prototype.getDataName = function() {
|
||||
return this['__dataName'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the name of this data node.
|
||||
* @param {string} value Name.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.AbstractFastDataNode.prototype.setDataName = function(value) {
|
||||
this['__dataName'] = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the path leading to this data node.
|
||||
* @return {string} Data path.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.AbstractFastDataNode.prototype.getDataPath = function() {
|
||||
var parentPath;
|
||||
if (this['__parent']) {
|
||||
parentPath = this['__parent'].getDataPath() + goog.ds.STR_PATH_SEPARATOR;
|
||||
} else {
|
||||
parentPath = '';
|
||||
}
|
||||
return parentPath + this.getDataName();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new fast data node, using the properties of root.
|
||||
* @param {Object} root JSON-like object to initialize data node from.
|
||||
* @param {string} dataName Name of this data node.
|
||||
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
|
||||
* @extends {goog.ds.AbstractFastDataNode}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.FastDataNode = function(root, dataName, opt_parent) {
|
||||
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
|
||||
this.extendWith_(root);
|
||||
};
|
||||
goog.inherits(goog.ds.FastDataNode, goog.ds.AbstractFastDataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Add all attributes of object to this data node.
|
||||
* @param {Object} object Object to add attributes from.
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.extendWith_ = function(object) {
|
||||
for (var key in object) {
|
||||
this[key] = object[key];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new FastDataNode structure initialized from object. This will
|
||||
* return an instance of the most suitable sub-class of FastDataNode.
|
||||
*
|
||||
* You should not modify object after creating a fast data node from it
|
||||
* or assume that changing object changes the data node. Doing so results
|
||||
* in undefined behaviour.
|
||||
*
|
||||
* @param {Object|number|boolean|string} object Object to initialize data
|
||||
* node from.
|
||||
* @param {string} dataName Name of data node.
|
||||
* @param {goog.ds.DataNode=} opt_parent Parent of data node.
|
||||
* @return {goog.ds.AbstractFastDataNode} Data node representing object.
|
||||
*/
|
||||
goog.ds.FastDataNode.fromJs = function(object, dataName, opt_parent) {
|
||||
if (goog.isArray(object)) {
|
||||
return new goog.ds.FastListNode(object, dataName, opt_parent);
|
||||
} else if (goog.isObject(object)) {
|
||||
return new goog.ds.FastDataNode(object, dataName, opt_parent);
|
||||
} else {
|
||||
return new goog.ds.PrimitiveFastDataNode(object || !!object,
|
||||
dataName,
|
||||
opt_parent);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static instance of an empty list.
|
||||
* @type {goog.ds.EmptyNodeList}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.FastDataNode.emptyList_ = new goog.ds.EmptyNodeList();
|
||||
|
||||
|
||||
/**
|
||||
* Not supported for normal FastDataNodes.
|
||||
* @param {*} value Value to set data node to.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.set = function(value) {
|
||||
throw 'Not implemented yet';
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.FastDataNode.prototype.getChildNodes = function(opt_selector) {
|
||||
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
|
||||
return this;
|
||||
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
|
||||
var child = this.getChildNode(opt_selector);
|
||||
return child ? new goog.ds.FastListNode([child], '') :
|
||||
new goog.ds.EmptyNodeList();
|
||||
} else {
|
||||
throw Error('Unsupported selector: ' + opt_selector);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes sure that a named child is wrapped in a data node structure.
|
||||
* @param {string} name Name of child to wrap.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.wrapChild_ = function(name) {
|
||||
var child = this[name];
|
||||
if (child != null && !child.getDataName) {
|
||||
this[name] = goog.ds.FastDataNode.fromJs(this[name], name, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a child node by name.
|
||||
* @param {string} name Name of child node.
|
||||
* @param {boolean=} opt_create Whether to create the child if it does not
|
||||
* exist.
|
||||
* @return {goog.ds.DataNode} Child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.getChildNode = function(name, opt_create) {
|
||||
this.wrapChild_(name);
|
||||
// this[name] always is a data node object, so using "||" is fine.
|
||||
var child = this[name] || null;
|
||||
if (child == null && opt_create) {
|
||||
child = new goog.ds.FastDataNode({}, name, this);
|
||||
this[name] = child;
|
||||
}
|
||||
return child;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a child node. Creates the child if it does not exist.
|
||||
*
|
||||
* Calling this function makes any child nodes previously obtained for name
|
||||
* invalid. You should not use these child nodes but instead obtain a new
|
||||
* instance by calling getChildNode.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.setChildNode = function(name, value) {
|
||||
if (value != null) {
|
||||
this[name] = value;
|
||||
} else {
|
||||
delete this[name];
|
||||
}
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath() +
|
||||
goog.ds.STR_PATH_SEPARATOR + name);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of a child node. By using this method you can avoid
|
||||
* the need to create PrimitiveFastData nodes.
|
||||
* @param {string} name Name of child node.
|
||||
* @return {Object} Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.getChildNodeValue = function(name) {
|
||||
var child = this[name];
|
||||
if (child != null) {
|
||||
return (child.getDataName ? child.get() : child);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this data node is a list. Always returns false for
|
||||
* instances of FastDataNode but may return true for subclasses.
|
||||
* @return {boolean} Whether this data node is array-like.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.isList = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a javascript object representation of this data node. You should
|
||||
* not modify the object returned by this function.
|
||||
* @return {Object} Javascript object representation of this data node.
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.getJsObject = function() {
|
||||
var result = {};
|
||||
for (var key in this) {
|
||||
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
|
||||
result[key] = (this[key]['__dataName'] ? this[key].getJsObject() :
|
||||
this[key]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a deep copy of this data node.
|
||||
* @return {goog.ds.FastDataNode} Clone of this data node.
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.clone = function() {
|
||||
return /** @type {goog.ds.FastDataNode} */(goog.ds.FastDataNode.fromJs(
|
||||
this.getJsObject(), this.getDataName()));
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Implementation of goog.ds.DataNodeList for FastDataNode.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Adds a child to this data node.
|
||||
* @param {goog.ds.DataNode} value Child node to add.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.add = function(value) {
|
||||
this.setChildNode(value.getDataName(), value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of this data node (if called without opt_key) or
|
||||
* gets a child node (if called with opt_key).
|
||||
* @param {string=} opt_key Name of child node.
|
||||
* @return {*} This data node or a child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.get = function(opt_key) {
|
||||
if (!goog.isDef(opt_key)) {
|
||||
// if there is no key, DataNode#get was called
|
||||
return this;
|
||||
} else {
|
||||
return this.getChildNode(opt_key);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a child node by index. This method has a complexity of O(n) where
|
||||
* n is the number of children. If you need a faster implementation of this
|
||||
* method, you should use goog.ds.FastListNode.
|
||||
* @param {number} index Index of child node (starting from 0).
|
||||
* @return {goog.ds.DataNode} Child node at specified index.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.getByIndex = function(index) {
|
||||
var i = 0;
|
||||
for (var key in this) {
|
||||
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
|
||||
if (i == index) {
|
||||
this.wrapChild_(key);
|
||||
return this[key];
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of child nodes. This method has a complexity of O(n) where
|
||||
* n is the number of children. If you need a faster implementation of this
|
||||
* method, you should use goog.ds.FastListNode.
|
||||
* @return {number} Number of child nodes.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.getCount = function() {
|
||||
var count = 0;
|
||||
for (var key in this) {
|
||||
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
// maybe cache this?
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a child node.
|
||||
* @param {string} name Name of child node.
|
||||
* @param {Object} value Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.setNode = function(name, value) {
|
||||
this.setChildNode(name, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastDataNode.prototype.removeNode = function(name) {
|
||||
delete this[name];
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new data node wrapping a primitive value.
|
||||
* @param {number|boolean|string} value Value the value to wrap.
|
||||
* @param {string} dataName name Name of this data node.
|
||||
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
|
||||
* @extends {goog.ds.AbstractFastDataNode}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode = function(value, dataName, opt_parent) {
|
||||
this.value_ = value;
|
||||
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
|
||||
};
|
||||
goog.inherits(goog.ds.PrimitiveFastDataNode, goog.ds.AbstractFastDataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of this data node.
|
||||
* @return {(boolean|number|string)} Value of this data node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.get = function() {
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets this data node to a new value.
|
||||
* @param {*} value Value to set data node to.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.set = function(value) {
|
||||
if (goog.isArray(value) || goog.isObject(value)) {
|
||||
throw Error('can only set PrimitiveFastDataNode to primitive values');
|
||||
}
|
||||
this.value_ = value;
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns child nodes of this data node. Always returns an unmodifiable,
|
||||
* empty list.
|
||||
* @return {goog.ds.DataNodeList} (Empty) list of child nodes.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.getChildNodes = function() {
|
||||
return goog.ds.FastDataNode.emptyList_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a child node by name. Always returns null.
|
||||
* @param {string} name Name of child node.
|
||||
* @return {goog.ds.DataNode} Child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.getChildNode = function(name) {
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of a child node. Always returns null.
|
||||
* @param {string} name Name of child node.
|
||||
* @return {Object} Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.getChildNodeValue = function(name) {
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Not supported by primitive data nodes.
|
||||
* @param {string} name Name of child node.
|
||||
* @param {Object} value Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.setChildNode =
|
||||
function(name, value) {
|
||||
throw Error('Cannot set a child node for a PrimitiveFastDataNode');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this data node is a list. Always returns false for
|
||||
* instances of PrimitiveFastDataNode.
|
||||
* @return {boolean} Whether this data node is array-like.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.isList = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a javascript object representation of this data node. You should
|
||||
* not modify the object returned by this function.
|
||||
* @return {*} Javascript object representation of this data node.
|
||||
*/
|
||||
goog.ds.PrimitiveFastDataNode.prototype.getJsObject = function() {
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new list node from an array.
|
||||
* @param {Array} values values hold by this list node.
|
||||
* @param {string} dataName name of this node.
|
||||
* @param {goog.ds.DataNode=} opt_parent parent of this node.
|
||||
* @extends {goog.ds.AbstractFastDataNode}
|
||||
* @constructor
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available. This implements DataNodeList
|
||||
// as well.
|
||||
goog.ds.FastListNode = function(values, dataName, opt_parent) {
|
||||
this.values_ = [];
|
||||
for (var i = 0; i < values.length; ++i) {
|
||||
var name = values[i].id || ('[' + i + ']');
|
||||
this.values_.push(goog.ds.FastDataNode.fromJs(values[i], name, this));
|
||||
if (values[i].id) {
|
||||
if (!this.map_) {
|
||||
this.map_ = {};
|
||||
}
|
||||
this.map_[values[i].id] = i;
|
||||
}
|
||||
}
|
||||
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
|
||||
};
|
||||
goog.inherits(goog.ds.FastListNode, goog.ds.AbstractFastDataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Not supported for FastListNodes.
|
||||
* @param {*} value Value to set data node to.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.set = function(value) {
|
||||
throw Error('Cannot set a FastListNode to a new value');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns child nodes of this data node. Currently, only supports
|
||||
* returning all children.
|
||||
* @return {goog.ds.DataNodeList} List of child nodes.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getChildNodes = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a child node by name.
|
||||
* @param {string} key Name of child node.
|
||||
* @param {boolean=} opt_create Whether to create the child if it does not
|
||||
* exist.
|
||||
* @return {goog.ds.DataNode} Child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getChildNode = function(key, opt_create) {
|
||||
var index = this.getKeyAsNumber_(key);
|
||||
if (index == null && this.map_) {
|
||||
index = this.map_[key];
|
||||
}
|
||||
if (index != null && this.values_[index]) {
|
||||
return this.values_[index];
|
||||
} else if (opt_create) {
|
||||
this.setChildNode(key, {});
|
||||
return this.getChildNode(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of a child node.
|
||||
* @param {string} key Name of child node.
|
||||
* @return {*} Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getChildNodeValue = function(key) {
|
||||
var child = this.getChildNode(key);
|
||||
return (child ? child.get() : null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tries to interpret key as a numeric index enclosed by square brakcets.
|
||||
* @param {string} key Key that should be interpreted as a number.
|
||||
* @return {?number} Numeric index or null if key is not of the form
|
||||
* described above.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getKeyAsNumber_ = function(key) {
|
||||
if (key.charAt(0) == '[' && key.charAt(key.length - 1) == ']') {
|
||||
return Number(key.substring(1, key.length - 1));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a child node. Creates the child if it does not exist. To set
|
||||
* children at a certain index, use a key of the form '[index]'. Note, that
|
||||
* you can only set values at existing numeric indices. To add a new node
|
||||
* to this list, you have to use the add method.
|
||||
*
|
||||
* Calling this function makes any child nodes previously obtained for name
|
||||
* invalid. You should not use these child nodes but instead obtain a new
|
||||
* instance by calling getChildNode.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.setChildNode = function(key, value) {
|
||||
var count = this.values_.length;
|
||||
if (value != null) {
|
||||
if (!value.getDataName) {
|
||||
value = goog.ds.FastDataNode.fromJs(value, key, this);
|
||||
}
|
||||
var index = this.getKeyAsNumber_(key);
|
||||
if (index != null) {
|
||||
if (index < 0 || index >= this.values_.length) {
|
||||
throw Error('List index out of bounds: ' + index);
|
||||
}
|
||||
this.values_[key] = value;
|
||||
} else {
|
||||
if (!this.map_) {
|
||||
this.map_ = {};
|
||||
}
|
||||
this.values_.push(value);
|
||||
this.map_[key] = this.values_.length - 1;
|
||||
}
|
||||
} else {
|
||||
this.removeNode(key);
|
||||
}
|
||||
var dm = goog.ds.DataManager.getInstance();
|
||||
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + key);
|
||||
if (this.values_.length != count) {
|
||||
this.listSizeChanged_();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fire data changes that are appropriate when the size of this list changes.
|
||||
* Should be called whenever the list size has changed.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.listSizeChanged_ = function() {
|
||||
var dm = goog.ds.DataManager.getInstance();
|
||||
dm.fireDataChange(this.getDataPath());
|
||||
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
|
||||
'count()');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this data node is a list. Always returns true.
|
||||
* @return {boolean} Whether this data node is array-like.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.isList = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a javascript object representation of this data node. You should
|
||||
* not modify the object returned by this function.
|
||||
* @return {Object} Javascript object representation of this data node.
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getJsObject = function() {
|
||||
var result = [];
|
||||
for (var i = 0; i < this.values_.length; ++i) {
|
||||
result.push(this.values_[i].getJsObject());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Implementation of goog.ds.DataNodeList for FastListNode.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Adds a child to this data node
|
||||
* @param {goog.ds.DataNode} value Child node to add.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.add = function(value) {
|
||||
if (!value.getDataName) {
|
||||
value = goog.ds.FastDataNode.fromJs(value,
|
||||
String('[' + (this.values_.length) + ']'), this);
|
||||
}
|
||||
this.values_.push(value);
|
||||
var dm = goog.ds.DataManager.getInstance();
|
||||
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
|
||||
'[' + (this.values_.length - 1) + ']');
|
||||
this.listSizeChanged_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of this data node (if called without opt_key) or
|
||||
* gets a child node (if called with opt_key).
|
||||
* @param {string=} opt_key Name of child node.
|
||||
* @return {Array|goog.ds.DataNode} Array of child nodes (if called without
|
||||
* opt_key), or a named child node otherwise.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.get = function(opt_key) {
|
||||
// if there are no arguments, DataNode.get was called
|
||||
if (!goog.isDef(opt_key)) {
|
||||
return this.values_;
|
||||
} else {
|
||||
return this.getChildNode(opt_key);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a child node by (numeric) index.
|
||||
* @param {number} index Index of child node (starting from 0).
|
||||
* @return {goog.ds.DataNode} Child node at specified index.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getByIndex = function(index) {
|
||||
var child = this.values_[index];
|
||||
return (child != null ? child : null); // never return undefined
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the number of child nodes.
|
||||
* @return {number} Number of child nodes.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.getCount = function() {
|
||||
return this.values_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a child node.
|
||||
* @param {string} name Name of child node.
|
||||
* @param {Object} value Value of child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.setNode = function(name, value) {
|
||||
throw Error('Setting child nodes of a FastListNode is not implemented, yet');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a child node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.removeNode = function(name) {
|
||||
var index = this.getKeyAsNumber_(name);
|
||||
if (index == null && this.map_) {
|
||||
index = this.map_[name];
|
||||
}
|
||||
if (index != null) {
|
||||
this.values_.splice(index, 1);
|
||||
if (this.map_) {
|
||||
var keyToDelete = null;
|
||||
for (var key in this.map_) {
|
||||
if (this.map_[key] == index) {
|
||||
keyToDelete = key;
|
||||
} else if (this.map_[key] > index) {
|
||||
--this.map_[key];
|
||||
}
|
||||
}
|
||||
if (keyToDelete) {
|
||||
delete this.map_[keyToDelete];
|
||||
}
|
||||
}
|
||||
var dm = goog.ds.DataManager.getInstance();
|
||||
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
|
||||
'[' + index + ']');
|
||||
this.listSizeChanged_();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index of a named child nodes. This method only works if
|
||||
* this list uses mixed name/indexed lookup, i.e. if its child node have
|
||||
* an 'id' attribute.
|
||||
* @param {string} name Name of child node to determine index of.
|
||||
* @return {number} Index of child node named name.
|
||||
*/
|
||||
goog.ds.FastListNode.prototype.indexOf = function(name) {
|
||||
var index = this.getKeyAsNumber_(name);
|
||||
if (index == null && this.map_) {
|
||||
index = this.map_[name];
|
||||
}
|
||||
if (index == null) {
|
||||
throw Error('Cannot determine index for: ' + name);
|
||||
}
|
||||
return /** @type {number} */(index);
|
||||
};
|
||||
@@ -0,0 +1,460 @@
|
||||
// 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 An implementation of DataNode for wrapping JS data.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ds.JsDataSource');
|
||||
goog.provide('goog.ds.JsPropertyDataSource');
|
||||
|
||||
goog.require('goog.ds.BaseDataNode');
|
||||
goog.require('goog.ds.BasicNodeList');
|
||||
goog.require('goog.ds.DataManager');
|
||||
goog.require('goog.ds.EmptyNodeList');
|
||||
goog.require('goog.ds.LoadState');
|
||||
|
||||
|
||||
/**
|
||||
* Data source whose backing is JavaScript data
|
||||
*
|
||||
* Names that are reserved for system use and shouldn't be used for data node
|
||||
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
|
||||
* undefined if these names are used.
|
||||
*
|
||||
* @param {Object} root The root JS node.
|
||||
* @param {string} dataName The name of this node relative to the parent node.
|
||||
* @param {Object=} opt_parent Optional parent of this JsDataSource.
|
||||
*
|
||||
* implements goog.ds.DataNode.
|
||||
* @constructor
|
||||
* @extends {goog.ds.DataNode}
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available.
|
||||
goog.ds.JsDataSource = function(root, dataName, opt_parent) {
|
||||
this.parent_ = opt_parent;
|
||||
this.dataName_ = dataName;
|
||||
this.setRoot(root);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The root JS object. Can be null.
|
||||
* @type {*}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.root_;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the root JS object
|
||||
* @param {Object} root The root JS object. Can be null.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.setRoot = function(root) {
|
||||
this.root_ = root;
|
||||
this.childNodeList_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set this data source to use list semantics. List data sources:
|
||||
* - Are assumed to have child nodes of all of the same type of data
|
||||
* - Fire data changes on the root node of the list whenever children
|
||||
* are added or removed
|
||||
* @param {?boolean} isList True to use list semantics.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.setIsList_ = function(isList) {
|
||||
this.isList_ = isList;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.JsDataSource.prototype.get = function() {
|
||||
return !goog.isObject(this.root_) ? this.root_ : this.getChildNodes();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of the node
|
||||
* @param {*} value The new value of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.set = function(value) {
|
||||
if (value && goog.isObject(this.root_)) {
|
||||
throw Error('Can\'t set group nodes to new values yet');
|
||||
}
|
||||
|
||||
if (this.parent_) {
|
||||
this.parent_.root_[this.dataName_] = value;
|
||||
}
|
||||
this.root_ = value;
|
||||
this.childNodeList_ = null;
|
||||
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* TODO(user) revisit lazy creation.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getChildNodes = function(opt_selector) {
|
||||
if (!this.root_) {
|
||||
return new goog.ds.EmptyNodeList();
|
||||
}
|
||||
|
||||
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
|
||||
this.createChildNodes_(false);
|
||||
return this.childNodeList_;
|
||||
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
|
||||
if (this.root_[opt_selector] != null) {
|
||||
return new goog.ds.BasicNodeList([this.getChildNode(opt_selector)]);
|
||||
} else {
|
||||
return new goog.ds.EmptyNodeList();
|
||||
}
|
||||
} else {
|
||||
throw Error('Selector not supported yet (' + opt_selector + ')');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the DataNodeList with the child nodes for this element.
|
||||
* Allows for only building list as needed.
|
||||
*
|
||||
* @param {boolean=} opt_force Whether to force recreating child nodes,
|
||||
* defaults to false.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.createChildNodes_ = function(opt_force) {
|
||||
if (this.childNodeList_ && !opt_force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!goog.isObject(this.root_)) {
|
||||
this.childNodeList_ = new goog.ds.EmptyNodeList();
|
||||
return;
|
||||
}
|
||||
|
||||
var childNodeList = new goog.ds.BasicNodeList();
|
||||
var newNode;
|
||||
if (goog.isArray(this.root_)) {
|
||||
var len = this.root_.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
// "id" is reserved node name that will map to a named child node
|
||||
// TODO(user) Configurable logic for choosing id node
|
||||
var node = this.root_[i];
|
||||
var id = node.id;
|
||||
var name = id != null ? String(id) : '[' + i + ']';
|
||||
newNode = new goog.ds.JsDataSource(node, name, this);
|
||||
childNodeList.add(newNode);
|
||||
}
|
||||
} else {
|
||||
for (var name in this.root_) {
|
||||
var obj = this.root_[name];
|
||||
// If the node is already a datasource, then add it.
|
||||
if (obj.getDataName) {
|
||||
childNodeList.add(obj);
|
||||
} else if (!goog.isFunction(obj)) {
|
||||
newNode = new goog.ds.JsDataSource(obj, name, this);
|
||||
childNodeList.add(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.childNodeList_ = childNodeList;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node
|
||||
* @param {string} name The node name.
|
||||
* @param {boolean=} opt_canCreate If true, can create child node.
|
||||
* @return {goog.ds.DataNode} The child node, or null if no node of
|
||||
* this name exists.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getChildNode = function(name, opt_canCreate) {
|
||||
if (!this.root_) {
|
||||
return null;
|
||||
}
|
||||
var node = /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
|
||||
if (!node && opt_canCreate) {
|
||||
var newObj = {};
|
||||
if (goog.isArray(this.root_)) {
|
||||
newObj['id'] = name;
|
||||
this.root_.push(newObj);
|
||||
} else {
|
||||
this.root_[name] = newObj;
|
||||
}
|
||||
node = new goog.ds.JsDataSource(newObj, name, this);
|
||||
if (this.childNodeList_) {
|
||||
this.childNodeList_.add(node);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a child node
|
||||
* @param {string} name The node name.
|
||||
* @return {Object} The value of the node, or null if no value or the child
|
||||
* node doesn't exist.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getChildNodeValue = function(name) {
|
||||
if (this.childNodeList_) {
|
||||
var node = this.getChildNodes().get(name);
|
||||
return node ? node.get() : null;
|
||||
} else if (this.root_) {
|
||||
return this.root_[name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a named child node of the current node.
|
||||
* If value is null, removes the child node.
|
||||
* @param {string} name The node name.
|
||||
* @param {Object} value The value to set, can be DataNode, object,
|
||||
* property, or null.
|
||||
* @return {Object} The child node, if set.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.setChildNode = function(name, value) {
|
||||
var removedPath = null;
|
||||
var node = null;
|
||||
var addedNode = false;
|
||||
|
||||
// Set node to the DataNode to add - if the value isn't already a DataNode,
|
||||
// creates a JsDataSource or JsPropertyDataSource wrapper
|
||||
if (value != null) {
|
||||
if (value.getDataName) {
|
||||
// The value is a DataNode. We must update its parent.
|
||||
node = value;
|
||||
node.parent_ = this;
|
||||
} else {
|
||||
if (goog.isArray(value) || goog.isObject(value)) {
|
||||
node = new goog.ds.JsDataSource(value, name, this);
|
||||
} else {
|
||||
node = new goog.ds.JsPropertyDataSource(
|
||||
/** @type {goog.ds.DataNode} */ (this.root_), name, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This logic will get cleaner once we can remove the backing array / object
|
||||
// and just rely on the childNodeList_. This is needed until dependent code
|
||||
// is cleaned up.
|
||||
// TODO(user) Remove backing array / object and just use childNodeList_
|
||||
|
||||
if (goog.isArray(this.root_)) {
|
||||
// To remove by name, need to create a map of the child nodes by ID
|
||||
this.createChildNodes_();
|
||||
var index = this.childNodeList_.indexOf(name);
|
||||
if (value == null) {
|
||||
// Remove the node
|
||||
var nodeToRemove = this.childNodeList_.get(name);
|
||||
if (nodeToRemove) {
|
||||
removedPath = nodeToRemove.getDataPath();
|
||||
}
|
||||
this.root_.splice(index, 1);
|
||||
} else {
|
||||
// Add the node
|
||||
if (index) {
|
||||
this.root_[index] = value;
|
||||
} else {
|
||||
this.root_.push(value);
|
||||
}
|
||||
}
|
||||
if (index == null) {
|
||||
addedNode = true;
|
||||
}
|
||||
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
|
||||
} else if (goog.isObject(this.root_)) {
|
||||
if (value == null) {
|
||||
// Remove the node
|
||||
this.createChildNodes_();
|
||||
var nodeToRemove = this.childNodeList_.get(name);
|
||||
if (nodeToRemove) {
|
||||
removedPath = nodeToRemove.getDataPath();
|
||||
}
|
||||
delete this.root_[name];
|
||||
} else {
|
||||
// Add the node
|
||||
if (!this.root_[name]) {
|
||||
addedNode = true;
|
||||
}
|
||||
this.root_[name] = value;
|
||||
}
|
||||
// Only need to update childNodeList_ if has been created already
|
||||
if (this.childNodeList_) {
|
||||
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
|
||||
}
|
||||
}
|
||||
|
||||
// Fire the event that the node changed
|
||||
var dm = goog.ds.DataManager.getInstance();
|
||||
if (node) {
|
||||
dm.fireDataChange(node.getDataPath());
|
||||
if (addedNode && this.isList()) {
|
||||
dm.fireDataChange(this.getDataPath());
|
||||
dm.fireDataChange(this.getDataPath() + '/count()');
|
||||
}
|
||||
} else if (removedPath) {
|
||||
dm.fireDataChange(removedPath);
|
||||
if (this.isList()) {
|
||||
dm.fireDataChange(this.getDataPath());
|
||||
dm.fireDataChange(this.getDataPath() + '/count()');
|
||||
}
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getDataName = function() {
|
||||
return this.dataName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Setthe name of the node relative to the parent node
|
||||
* @param {string} dataName The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.setDataName = function(dataName) {
|
||||
this.dataName_ = dataName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the a qualified data path to this node
|
||||
* @return {string} The data path.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getDataPath = function() {
|
||||
var parentPath = '';
|
||||
if (this.parent_) {
|
||||
parentPath = this.parent_.getDataPath() + goog.ds.STR_PATH_SEPARATOR;
|
||||
}
|
||||
|
||||
return parentPath + this.dataName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.load = function() {
|
||||
// Nothing to do
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* TODO(user) Discuss null value handling
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.getLoadState = function() {
|
||||
return (this.root_ == null) ? goog.ds.LoadState.NOT_LOADED :
|
||||
goog.ds.LoadState.LOADED;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the value of this node is a homogeneous list of data
|
||||
* @return {boolean} True if a list.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsDataSource.prototype.isList = function() {
|
||||
return this.isList_ != null ? this.isList_ : goog.isArray(this.root_);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Data source for JavaScript properties that arent objects. Contains reference
|
||||
* to parent object so that you can set the vaule
|
||||
*
|
||||
* @param {goog.ds.DataNode} parent Parent object.
|
||||
* @param {string} dataName Name of this property.
|
||||
* @param {goog.ds.DataNode=} opt_parentDataNode The parent data node. If
|
||||
* omitted, assumes that the parent object is the parent data node.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.ds.BaseDataNode}
|
||||
*/
|
||||
goog.ds.JsPropertyDataSource = function(parent, dataName, opt_parentDataNode) {
|
||||
goog.ds.BaseDataNode.call(this);
|
||||
this.dataName_ = dataName;
|
||||
this.parent_ = parent;
|
||||
this.parentDataNode_ = opt_parentDataNode || this.parent_;
|
||||
};
|
||||
goog.inherits(goog.ds.JsPropertyDataSource, goog.ds.BaseDataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Get the value of the node
|
||||
* @return {Object} The value of the node, or null if no value.
|
||||
*/
|
||||
goog.ds.JsPropertyDataSource.prototype.get = function() {
|
||||
return this.parent_[this.dataName_];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of the node
|
||||
* @param {Object} value The new value of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsPropertyDataSource.prototype.set = function(value) {
|
||||
var oldValue = this.parent_[this.dataName_];
|
||||
this.parent_[this.dataName_] = value;
|
||||
|
||||
if (oldValue != value) {
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsPropertyDataSource.prototype.getDataName = function() {
|
||||
return this.dataName_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.JsPropertyDataSource.prototype.getParent = function() {
|
||||
return this.parentDataNode_;
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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 Implementation of DataNode for wrapping JSON data.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ds.JsonDataSource');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.ds.DataManager');
|
||||
goog.require('goog.ds.JsDataSource');
|
||||
goog.require('goog.ds.LoadState');
|
||||
goog.require('goog.ds.logger');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Data source whose backing is a JSON-like service, in which
|
||||
* retreiving the resource specified by URL with the additional parameter
|
||||
* callback. The resource retreived is executable JavaScript that
|
||||
* makes a call to the named function with a JavaScript object literal
|
||||
* as the only parameter.
|
||||
*
|
||||
* Example URI could be:
|
||||
* http://www.google.com/data/search?q=monkey&callback=mycb
|
||||
* which might return the JS:
|
||||
* mycb({searchresults:
|
||||
* [{uri: 'http://www.monkey.com', title: 'Site About Monkeys'}]});
|
||||
*
|
||||
* TODO(user): Evaluate using goog.net.Jsonp here.
|
||||
*
|
||||
* A URI of an empty string will mean that no request is made
|
||||
* and the data source will be a data source with no child nodes
|
||||
*
|
||||
* @param {string|goog.Uri} uri URI for the request.
|
||||
* @param {string} name Name of the datasource.
|
||||
* @param {string=} opt_callbackParamName The parameter name that is used to
|
||||
* specify the callback. Defaults to 'callback'.
|
||||
*
|
||||
* @extends {goog.ds.JsDataSource}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.JsonDataSource = function(uri, name, opt_callbackParamName) {
|
||||
goog.ds.JsDataSource.call(this, null, name, null);
|
||||
if (uri) {
|
||||
this.uri_ = new goog.Uri(uri);
|
||||
} else {
|
||||
this.uri_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback parameter name that is added to the uri.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.callbackParamName_ = opt_callbackParamName || 'callback';
|
||||
|
||||
};
|
||||
goog.inherits(goog.ds.JsonDataSource, goog.ds.JsDataSource);
|
||||
|
||||
|
||||
/**
|
||||
* Default load state is NOT_LOADED
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsonDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
|
||||
|
||||
|
||||
/**
|
||||
* Map of all data sources, needed for callbacks
|
||||
* Doesn't work unless dataSources is exported (not renamed)
|
||||
*/
|
||||
goog.ds.JsonDataSource['dataSources'] = {};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node.
|
||||
* Fires the JsonDataSource
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsonDataSource.prototype.load = function() {
|
||||
if (this.uri_) {
|
||||
// NOTE: "dataSources" is expose above by name so that it will not be
|
||||
// renamed. It should therefore be accessed via array notation here so
|
||||
// that it also doesn't get renamed and stops the compiler from complaining
|
||||
goog.ds.JsonDataSource['dataSources'][this.dataName_] = this;
|
||||
goog.ds.logger.info('Sending JS request for DataSource ' +
|
||||
this.getDataName() + ' to ' + this.uri_);
|
||||
|
||||
this.loadState_ = goog.ds.LoadState.LOADING;
|
||||
|
||||
var uriToCall = new goog.Uri(this.uri_);
|
||||
uriToCall.setParameterValue(this.callbackParamName_,
|
||||
'JsonReceive.' + this.dataName_);
|
||||
|
||||
goog.global['JsonReceive'][this.dataName_] =
|
||||
goog.bind(this.receiveData, this);
|
||||
|
||||
var scriptEl = goog.dom.createDom('script', {'src': uriToCall});
|
||||
goog.dom.getElementsByTagNameAndClass('head')[0].appendChild(scriptEl);
|
||||
} else {
|
||||
this.root_ = {};
|
||||
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsonDataSource.prototype.getLoadState = function() {
|
||||
return this.loadState_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Receives data from a Json request
|
||||
* @param {Object} obj The JSON data.
|
||||
*/
|
||||
goog.ds.JsonDataSource.prototype.receiveData = function(obj) {
|
||||
this.setRoot(obj);
|
||||
this.loadState_ = goog.ds.LoadState.LOADED;
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Temp variable to hold callbacks
|
||||
* until BUILD supports multiple externs.js files
|
||||
*/
|
||||
goog.global['JsonReceive'] = {};
|
||||
@@ -0,0 +1,193 @@
|
||||
// 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
|
||||
* DataSource implementation that uses XMLHttpRequest as transport, with
|
||||
* response as serialized JS object (not required to be JSON) that can
|
||||
* be evaluated and set to a variable.
|
||||
*
|
||||
* Response can have unexecutable starting/ending text to prevent inclusion
|
||||
* using <script src="...">
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ds.JsXmlHttpDataSource');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.ds.DataManager');
|
||||
goog.require('goog.ds.FastDataNode');
|
||||
goog.require('goog.ds.LoadState');
|
||||
goog.require('goog.ds.logger');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.EventType');
|
||||
goog.require('goog.net.XhrIo');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Similar to JsonDataSource, with using XMLHttpRequest for transport
|
||||
* Currently requires the result be a JS object that can be evaluated and
|
||||
* set to a variable and doesn't require strict JSON notation.
|
||||
*
|
||||
* @param {string || goog.Uri} uri URI for the request.
|
||||
* @param {string} name Name of the datasource.
|
||||
* @param {string=} opt_startText Text to expect/strip before JS response.
|
||||
* @param {string=} opt_endText Text to expect/strip after JS response.
|
||||
* @param {boolean=} opt_usePost If true, use POST. Defaults to false (GET).
|
||||
*
|
||||
* @extends {goog.ds.FastDataNode}
|
||||
* @constructor
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource = function(uri, name, opt_startText, opt_endText,
|
||||
opt_usePost) {
|
||||
goog.ds.FastDataNode.call(this, {}, name, null);
|
||||
if (uri) {
|
||||
this.uri_ = new goog.Uri(uri);
|
||||
this.xhr_ = new goog.net.XhrIo();
|
||||
this.usePost_ = !!opt_usePost;
|
||||
|
||||
goog.events.listen(this.xhr_, goog.net.EventType.COMPLETE,
|
||||
this.completed_, false, this);
|
||||
} else {
|
||||
this.uri_ = null;
|
||||
}
|
||||
this.startText_ = opt_startText;
|
||||
this.endText_ = opt_endText;
|
||||
};
|
||||
goog.inherits(goog.ds.JsXmlHttpDataSource, goog.ds.FastDataNode);
|
||||
|
||||
|
||||
/**
|
||||
* Delimiter for start of JSON data in response.
|
||||
* null = starts at first character of response
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.startText_;
|
||||
|
||||
|
||||
/**
|
||||
* Delimiter for end of JSON data in response.
|
||||
* null = ends at last character of response
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.endText_;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.getLoadState = function() {
|
||||
return this.loadState_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the request data. This can be used if it is required to
|
||||
* send a specific body rather than build the body from the query
|
||||
* parameters. Only used in POST requests.
|
||||
* @param {string} data The data to send in the request body.
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.setQueryData = function(data) {
|
||||
this.queryData_ = data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node.
|
||||
* Fires the JsonDataSource
|
||||
* @override
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.load = function() {
|
||||
goog.ds.logger.info('Sending JS request for DataSource ' +
|
||||
this.getDataName() + ' to ' + this.uri_);
|
||||
|
||||
if (this.uri_) {
|
||||
if (this.usePost_) {
|
||||
|
||||
var queryData;
|
||||
if (!this.queryData_) {
|
||||
queryData = this.uri_.getQueryData().toString();
|
||||
} else {
|
||||
queryData = this.queryData_;
|
||||
}
|
||||
|
||||
var uriNoQuery = this.uri_.clone();
|
||||
uriNoQuery.setQueryData(null);
|
||||
this.xhr_.send(String(uriNoQuery), 'POST', queryData);
|
||||
} else {
|
||||
this.xhr_.send(String(this.uri_));
|
||||
}
|
||||
} else {
|
||||
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called on successful request.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.success_ = function() {
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Completed callback. Loads data if successful, otherwise sets
|
||||
* state to FAILED
|
||||
* @param {goog.events.Event} e Event object, Xhr is target.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.JsXmlHttpDataSource.prototype.completed_ = function(e) {
|
||||
if (this.xhr_.isSuccess()) {
|
||||
goog.ds.logger.info('Got data for DataSource ' + this.getDataName());
|
||||
var text = this.xhr_.getResponseText();
|
||||
|
||||
// Look for start and end token and trim text
|
||||
if (this.startText_) {
|
||||
var startpos = text.indexOf(this.startText_);
|
||||
text = text.substring(startpos + this.startText_.length);
|
||||
}
|
||||
if (this.endText_) {
|
||||
var endpos = text.lastIndexOf(this.endText_);
|
||||
text = text.substring(0, endpos);
|
||||
}
|
||||
|
||||
// Eval result
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var jsonObj = /** @type {Object} */ (eval('[' + text + '][0]'));
|
||||
this.extendWith_(jsonObj);
|
||||
this.loadState_ = goog.ds.LoadState.LOADED;
|
||||
}
|
||||
catch (ex) {
|
||||
// Invalid JS
|
||||
this.loadState_ = goog.ds.LoadState.FAILED;
|
||||
goog.ds.logger.severe('Failed to parse data: ' + ex.message);
|
||||
}
|
||||
|
||||
// Call on a timer to avoid threading issues on IE.
|
||||
goog.global.setTimeout(goog.bind(this.success_, this), 0);
|
||||
} else {
|
||||
goog.ds.logger.info('Data retrieve failed for DataSource ' +
|
||||
this.getDataName());
|
||||
this.loadState_ = goog.ds.LoadState.FAILED;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,413 @@
|
||||
// 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
|
||||
* Implementations of DataNode for wrapping XML data.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ds.XmlDataSource');
|
||||
goog.provide('goog.ds.XmlHttpDataSource');
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.xml');
|
||||
goog.require('goog.ds.BasicNodeList');
|
||||
goog.require('goog.ds.DataManager');
|
||||
goog.require('goog.ds.LoadState');
|
||||
goog.require('goog.ds.logger');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Data source whose backing is an xml node
|
||||
*
|
||||
* @param {Node} node The XML node. Can be null.
|
||||
* @param {goog.ds.XmlDataSource} parent Parent of XML element. Can be null.
|
||||
* @param {string=} opt_name The name of this node relative to the parent node.
|
||||
*
|
||||
* @extends {goog.ds.DataNode}
|
||||
* @constructor
|
||||
*/
|
||||
// TODO(arv): Use interfaces when available.
|
||||
goog.ds.XmlDataSource = function(node, parent, opt_name) {
|
||||
this.parent_ = parent;
|
||||
this.dataName_ = opt_name || (node ? node.nodeName : '');
|
||||
this.setNode_(node);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constant to select XML attributes for getChildNodes
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_ = '@*';
|
||||
|
||||
|
||||
/**
|
||||
* Set the current root nodeof the data source.
|
||||
* Can be an attribute node, text node, or element node
|
||||
* @param {Node} node The node. Can be null.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.setNode_ = function(node) {
|
||||
this.node_ = node;
|
||||
if (node != null) {
|
||||
switch (node.nodeType) {
|
||||
case goog.dom.NodeType.ATTRIBUTE:
|
||||
case goog.dom.NodeType.TEXT:
|
||||
this.value_ = node.nodeValue;
|
||||
break;
|
||||
case goog.dom.NodeType.ELEMENT:
|
||||
if (node.childNodes.length == 1 &&
|
||||
node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
|
||||
this.value_ = node.firstChild.nodeValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the DataNodeList with the child nodes for this element.
|
||||
* Allows for only building list as needed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.createChildNodes_ = function() {
|
||||
if (this.childNodeList_) {
|
||||
return;
|
||||
}
|
||||
var childNodeList = new goog.ds.BasicNodeList();
|
||||
if (this.node_ != null) {
|
||||
var childNodes = this.node_.childNodes;
|
||||
for (var i = 0, childNode; childNode = childNodes[i]; i++) {
|
||||
if (childNode.nodeType != goog.dom.NodeType.TEXT ||
|
||||
!goog.ds.XmlDataSource.isEmptyTextNodeValue_(childNode.nodeValue)) {
|
||||
var newNode = new goog.ds.XmlDataSource(childNode,
|
||||
this, childNode.nodeName);
|
||||
childNodeList.add(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.childNodeList_ = childNodeList;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the DataNodeList with the attributes for the element
|
||||
* Allows for only building list as needed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.createAttributes_ = function() {
|
||||
if (this.attributes_) {
|
||||
return;
|
||||
}
|
||||
var attributes = new goog.ds.BasicNodeList();
|
||||
if (this.node_ != null && this.node_.attributes != null) {
|
||||
var atts = this.node_.attributes;
|
||||
for (var i = 0, att; att = atts[i]; i++) {
|
||||
var newNode = new goog.ds.XmlDataSource(att, this, att.nodeName);
|
||||
attributes.add(newNode);
|
||||
}
|
||||
}
|
||||
this.attributes_ = attributes;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the value of the node
|
||||
* @return {Object} The value of the node, or null if no value.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.get = function() {
|
||||
this.createChildNodes_();
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the value of the node
|
||||
* @param {*} value The new value of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.set = function(value) {
|
||||
throw Error('Can\'t set on XmlDataSource yet');
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ds.XmlDataSource.prototype.getChildNodes = function(opt_selector) {
|
||||
if (opt_selector && opt_selector ==
|
||||
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_) {
|
||||
this.createAttributes_();
|
||||
return this.attributes_;
|
||||
} else if (opt_selector == null ||
|
||||
opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
|
||||
this.createChildNodes_();
|
||||
return this.childNodeList_;
|
||||
} else {
|
||||
throw Error('Unsupported selector');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a named child node of the current node
|
||||
* @param {string} name The node name.
|
||||
* @return {goog.ds.DataNode} The child node, or null if
|
||||
* no node of this name exists.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.getChildNode = function(name) {
|
||||
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
|
||||
var att = this.node_.getAttributeNode(name.substring(1));
|
||||
return att ? new goog.ds.XmlDataSource(att, this) : null;
|
||||
} else {
|
||||
return /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of a child node
|
||||
* @param {string} name The node name.
|
||||
* @return {*} The value of the node, or null if no value or the child node
|
||||
* doesn't exist.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.getChildNodeValue = function(name) {
|
||||
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
|
||||
var node = this.node_.getAttributeNode(name.substring(1));
|
||||
return node ? node.nodeValue : null;
|
||||
} else {
|
||||
var node = this.getChildNode(name);
|
||||
return node ? node.get() : null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the node relative to the parent node
|
||||
* @return {string} The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.getDataName = function() {
|
||||
return this.dataName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Setthe name of the node relative to the parent node
|
||||
* @param {string} name The name of the node.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.setDataName = function(name) {
|
||||
this.dataName_ = name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the a qualified data path to this node
|
||||
* @return {string} The data path.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.getDataPath = function() {
|
||||
var parentPath = '';
|
||||
if (this.parent_) {
|
||||
parentPath = this.parent_.getDataPath() +
|
||||
(this.dataName_.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
|
||||
goog.ds.STR_PATH_SEPARATOR);
|
||||
}
|
||||
|
||||
return parentPath + this.dataName_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.load = function() {
|
||||
// Nothing to do
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlDataSource.prototype.getLoadState = function() {
|
||||
return this.node_ ? goog.ds.LoadState.LOADED : goog.ds.LoadState.NOT_LOADED;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a node is an empty text node. Nodes consisting of only white
|
||||
* space (#x20, #xD, #xA, #x9) can generally be collapsed to a zero length
|
||||
* text string.
|
||||
* @param {string} str String to match.
|
||||
* @return {boolean} True if string equates to empty text node.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.isEmptyTextNodeValue_ = function(str) {
|
||||
return /^[\r\n\t ]*$/.test(str);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an XML document with one empty node.
|
||||
* Useful for places where you need a node that
|
||||
* can be queried against.
|
||||
*
|
||||
* @return {Document} Document with one empty node.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlDataSource.createChildlessDocument_ = function() {
|
||||
return goog.dom.xml.createDocument('nothing');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Data source whose backing is an XMLHttpRequest,
|
||||
*
|
||||
* A URI of an empty string will mean that no request is made
|
||||
* and the data source will be a single, empty node.
|
||||
*
|
||||
* @param {(string,goog.Uri)} uri URL of the XMLHttpRequest.
|
||||
* @param {string} name Name of the datasource.
|
||||
*
|
||||
* implements goog.ds.XmlHttpDataSource.
|
||||
* @constructor
|
||||
* @extends {goog.ds.XmlDataSource}
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource = function(uri, name) {
|
||||
goog.ds.XmlDataSource.call(this, null, null, name);
|
||||
if (uri) {
|
||||
this.uri_ = new goog.Uri(uri);
|
||||
} else {
|
||||
this.uri_ = null;
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.ds.XmlHttpDataSource, goog.ds.XmlDataSource);
|
||||
|
||||
|
||||
/**
|
||||
* Default load state is NOT_LOADED
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
|
||||
|
||||
|
||||
/**
|
||||
* Load or reload the backing data for this node.
|
||||
* Fires the XMLHttpRequest
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.load = function() {
|
||||
if (this.uri_) {
|
||||
goog.ds.logger.info('Sending XML request for DataSource ' +
|
||||
this.getDataName() + ' to ' + this.uri_);
|
||||
this.loadState_ = goog.ds.LoadState.LOADING;
|
||||
|
||||
goog.net.XhrIo.send(this.uri_, goog.bind(this.complete_, this));
|
||||
} else {
|
||||
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
|
||||
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the state of the backing data for this node
|
||||
* @return {goog.ds.LoadState} The state.
|
||||
* @override
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.getLoadState = function() {
|
||||
return this.loadState_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the completion of an XhrIo request. Dispatches to success or load
|
||||
* based on the result.
|
||||
* @param {!goog.events.Event} e The XhrIo event object.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.complete_ = function(e) {
|
||||
var xhr = /** @type {goog.net.XhrIo} */ (e.target);
|
||||
if (xhr && xhr.isSuccess()) {
|
||||
this.success_(xhr);
|
||||
} else {
|
||||
this.failure_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Success result. Checks whether valid XML was returned
|
||||
* and sets the XML and loadstate.
|
||||
*
|
||||
* @param {!goog.net.XhrIo} xhr The successful XhrIo object.
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.success_ = function(xhr) {
|
||||
goog.ds.logger.info('Got data for DataSource ' + this.getDataName());
|
||||
var xml = xhr.getResponseXml();
|
||||
|
||||
// Fix for case where IE returns valid XML as text but
|
||||
// doesn't parse by default
|
||||
if (xml && !xml.hasChildNodes() &&
|
||||
goog.isObject(xhr.getResponseText())) {
|
||||
xml = goog.dom.xml.loadXml(xhr.getResponseText());
|
||||
}
|
||||
// Failure result
|
||||
if (!xml || !xml.hasChildNodes()) {
|
||||
this.loadState_ = goog.ds.LoadState.FAILED;
|
||||
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
|
||||
} else {
|
||||
this.loadState_ = goog.ds.LoadState.LOADED;
|
||||
this.node_ = xml.documentElement;
|
||||
}
|
||||
|
||||
if (this.getDataName()) {
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Failure result
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.ds.XmlHttpDataSource.prototype.failure_ = function() {
|
||||
goog.ds.logger.info('Data retrieve failed for DataSource ' +
|
||||
this.getDataName());
|
||||
|
||||
this.loadState_ = goog.ds.LoadState.FAILED;
|
||||
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
|
||||
|
||||
if (this.getDataName()) {
|
||||
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
// 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 Typedefs for working with dates.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.date.DateLike');
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {(Date|goog.date.Date)}
|
||||
*/
|
||||
goog.date.DateLike;
|
||||
@@ -0,0 +1,413 @@
|
||||
// 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 Date range data structure. Based loosely on
|
||||
* com.google.common.util.DateRange.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.date.DateRange');
|
||||
goog.provide('goog.date.DateRange.Iterator');
|
||||
goog.provide('goog.date.DateRange.StandardDateRangeKeys');
|
||||
|
||||
goog.require('goog.date.Date');
|
||||
goog.require('goog.date.Interval');
|
||||
goog.require('goog.iter.Iterator');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a date range.
|
||||
* @constructor
|
||||
* @param {goog.date.Date} startDate The first date in the range.
|
||||
* @param {goog.date.Date} endDate The last date in the range.
|
||||
*/
|
||||
goog.date.DateRange = function(startDate, endDate) {
|
||||
/**
|
||||
* The first date in the range.
|
||||
* @type {goog.date.Date}
|
||||
* @private
|
||||
*/
|
||||
this.startDate_ = startDate;
|
||||
|
||||
/**
|
||||
* The last date in the range.
|
||||
* @type {goog.date.Date}
|
||||
* @private
|
||||
*/
|
||||
this.endDate_ = endDate;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The first possible day, as far as this class is concerned.
|
||||
* @type {goog.date.Date}
|
||||
*/
|
||||
goog.date.DateRange.MINIMUM_DATE = new goog.date.Date(0000, 0, 1);
|
||||
|
||||
|
||||
/**
|
||||
* The last possible day, as far as this class is concerned.
|
||||
* @type {goog.date.Date}
|
||||
*/
|
||||
goog.date.DateRange.MAXIMUM_DATE = new goog.date.Date(9999, 11, 31);
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.date.Date} The first date in the range.
|
||||
*/
|
||||
goog.date.DateRange.prototype.getStartDate = function() {
|
||||
return this.startDate_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.date.Date} The last date in the range.
|
||||
*/
|
||||
goog.date.DateRange.prototype.getEndDate = function() {
|
||||
return this.endDate_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.iter.Iterator} An iterator over the date range.
|
||||
*/
|
||||
goog.date.DateRange.prototype.iterator = function() {
|
||||
return new goog.date.DateRange.Iterator(this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests two {@link goog.date.DateRange} objects for equality.
|
||||
* @param {goog.date.DateRange} a A date range.
|
||||
* @param {goog.date.DateRange} b A date range.
|
||||
* @return {boolean} Whether |a| is the same range as |b|.
|
||||
*/
|
||||
goog.date.DateRange.equals = function(a, b) {
|
||||
// Test for same object reference; type conversion is irrelevant.
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.startDate_.equals(b.startDate_) && a.endDate_.equals(b.endDate_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates a date that is a number of days after a date. Does not modify its
|
||||
* input.
|
||||
* @param {goog.date.Date} date The input date.
|
||||
* @param {number} offset Number of days.
|
||||
* @return {goog.date.Date} The date that is |offset| days after |date|.
|
||||
* @private
|
||||
*/
|
||||
goog.date.DateRange.offsetInDays_ = function(date, offset) {
|
||||
var newDate = date.clone();
|
||||
newDate.add(new goog.date.Interval(goog.date.Interval.DAYS, offset));
|
||||
return newDate;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the Monday before a date. If the input is a Monday, returns the
|
||||
* input. Does not modify its input.
|
||||
* @param {goog.date.Date} date The input date.
|
||||
* @return {goog.date.Date} If |date| is a Monday, return |date|; otherwise
|
||||
* return the Monday before |date|.
|
||||
* @private
|
||||
*/
|
||||
goog.date.DateRange.currentOrLastMonday_ = function(date) {
|
||||
var newDate = date.clone();
|
||||
newDate.add(new goog.date.Interval(goog.date.Interval.DAYS,
|
||||
-newDate.getIsoWeekday()));
|
||||
return newDate;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates a date that is a number of months after the first day in the
|
||||
* month that contains its input. Does not modify its input.
|
||||
* @param {goog.date.Date} date The input date.
|
||||
* @param {number} offset Number of months.
|
||||
* @return {goog.date.Date} The date that is |offset| months after the first
|
||||
* day in the month that contains |date|.
|
||||
* @private
|
||||
*/
|
||||
goog.date.DateRange.offsetInMonths_ = function(date, offset) {
|
||||
var newDate = date.clone();
|
||||
newDate.setDate(1);
|
||||
newDate.add(new goog.date.Interval(goog.date.Interval.MONTHS, offset));
|
||||
return newDate;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range from yesterday to yesterday.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that includes only yesterday.
|
||||
*/
|
||||
goog.date.DateRange.yesterday = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
|
||||
return new goog.date.DateRange(yesterday, yesterday);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range from today to today.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that includes only today.
|
||||
*/
|
||||
goog.date.DateRange.today = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
return new goog.date.DateRange(today, today);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range that includes the seven days that end yesterday.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that includes the seven days that
|
||||
* end yesterday.
|
||||
*/
|
||||
goog.date.DateRange.last7Days = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
var yesterday = goog.date.DateRange.offsetInDays_(today, -1);
|
||||
return new goog.date.DateRange(goog.date.DateRange.offsetInDays_(today, -7),
|
||||
yesterday);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range that starts the first of this month and ends the last day
|
||||
* of this month.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that starts the first of this month
|
||||
* and ends the last day of this month.
|
||||
*/
|
||||
goog.date.DateRange.thisMonth = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
return new goog.date.DateRange(
|
||||
goog.date.DateRange.offsetInMonths_(today, 0),
|
||||
goog.date.DateRange.offsetInDays_(
|
||||
goog.date.DateRange.offsetInMonths_(today, 1),
|
||||
-1));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range that starts the first of last month and ends the last day
|
||||
* of last month.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that starts the first of last month
|
||||
* and ends the last day of last month.
|
||||
*/
|
||||
goog.date.DateRange.lastMonth = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
return new goog.date.DateRange(
|
||||
goog.date.DateRange.offsetInMonths_(today, -1),
|
||||
goog.date.DateRange.offsetInDays_(
|
||||
goog.date.DateRange.offsetInMonths_(today, 0),
|
||||
-1));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the seven-day range that starts on the first day of the week
|
||||
* (see {@link goog.i18n.DateTimeSymbols.FIRSTDAYOFWEEK}) on or before today.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that starts the Monday on or before
|
||||
* today and ends the Sunday on or after today.
|
||||
*/
|
||||
goog.date.DateRange.thisWeek = function(opt_today) {
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
var iso = today.getIsoWeekday();
|
||||
var firstDay = today.getFirstDayOfWeek();
|
||||
var i18nFirstDay = (iso >= firstDay) ? iso - firstDay : iso + (7 - firstDay);
|
||||
var start = goog.date.DateRange.offsetInDays_(today, -i18nFirstDay);
|
||||
var end = goog.date.DateRange.offsetInDays_(start, 6);
|
||||
return new goog.date.DateRange(start, end);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the seven-day range that ends the day before the first day of
|
||||
* the week (see {@link goog.i18n.DateTimeSymbols.FIRSTDAYOFWEEK}) that
|
||||
* contains today.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that starts seven days before the
|
||||
* Monday on or before today and ends the Sunday on or before yesterday.
|
||||
*/
|
||||
goog.date.DateRange.lastWeek = function(opt_today) {
|
||||
var thisWeek = goog.date.DateRange.thisWeek(opt_today);
|
||||
var start = goog.date.DateRange.offsetInDays_(thisWeek.getStartDate(), -7);
|
||||
var end = goog.date.DateRange.offsetInDays_(thisWeek.getEndDate(), -7);
|
||||
return new goog.date.DateRange(start, end);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range that starts seven days before the Monday on or before
|
||||
* today and ends the Friday before today.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that starts seven days before the
|
||||
* Monday on or before today and ends the Friday before today.
|
||||
*/
|
||||
goog.date.DateRange.lastBusinessWeek = function(opt_today) {
|
||||
// TODO(user): should be i18nized.
|
||||
var today = goog.date.DateRange.cloneOrCreate_(opt_today);
|
||||
var start = goog.date.DateRange.offsetInDays_(today,
|
||||
- 7 - today.getIsoWeekday());
|
||||
var end = goog.date.DateRange.offsetInDays_(start, 4);
|
||||
return new goog.date.DateRange(start, end);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range that includes all days between January 1, 1900 and
|
||||
* December 31, 9999.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The range that includes all days between
|
||||
* January 1, 1900 and December 31, 9999.
|
||||
*/
|
||||
goog.date.DateRange.allTime = function(opt_today) {
|
||||
return new goog.date.DateRange(
|
||||
goog.date.DateRange.MINIMUM_DATE,
|
||||
goog.date.DateRange.MAXIMUM_DATE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Standard date range keys. Equivalent to the enum IDs in
|
||||
* DateRange.java http://go/datarange.java
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.date.DateRange.StandardDateRangeKeys = {
|
||||
YESTERDAY: 'yesterday',
|
||||
TODAY: 'today',
|
||||
LAST_7_DAYS: 'last7days',
|
||||
THIS_MONTH: 'thismonth',
|
||||
LAST_MONTH: 'lastmonth',
|
||||
THIS_WEEK: 'thisweek',
|
||||
LAST_WEEK: 'lastweek',
|
||||
LAST_BUSINESS_WEEK: 'lastbusinessweek',
|
||||
ALL_TIME: 'alltime'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} dateRangeKey A standard date range key.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {goog.date.DateRange} The date range that corresponds to that key.
|
||||
* @throws {Error} If no standard date range with that key exists.
|
||||
*/
|
||||
goog.date.DateRange.standardDateRange = function(dateRangeKey, opt_today) {
|
||||
switch (dateRangeKey) {
|
||||
case goog.date.DateRange.StandardDateRangeKeys.YESTERDAY:
|
||||
return goog.date.DateRange.yesterday(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.TODAY:
|
||||
return goog.date.DateRange.today(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.LAST_7_DAYS:
|
||||
return goog.date.DateRange.last7Days(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.THIS_MONTH:
|
||||
return goog.date.DateRange.thisMonth(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.LAST_MONTH:
|
||||
return goog.date.DateRange.lastMonth(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.THIS_WEEK:
|
||||
return goog.date.DateRange.thisWeek(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.LAST_WEEK:
|
||||
return goog.date.DateRange.lastWeek(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.LAST_BUSINESS_WEEK:
|
||||
return goog.date.DateRange.lastBusinessWeek(opt_today);
|
||||
|
||||
case goog.date.DateRange.StandardDateRangeKeys.ALL_TIME:
|
||||
return goog.date.DateRange.allTime(opt_today);
|
||||
|
||||
default:
|
||||
throw Error('no such date range key: ' + dateRangeKey);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones or creates new.
|
||||
* @param {goog.date.Date=} opt_today The date to consider today.
|
||||
* Defaults to today.
|
||||
* @return {!goog.date.Date} cloned or new.
|
||||
* @private
|
||||
*/
|
||||
goog.date.DateRange.cloneOrCreate_ = function(opt_today) {
|
||||
return opt_today ? opt_today.clone() : new goog.date.Date();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an iterator over the dates in a {@link goog.date.DateRange}.
|
||||
* @constructor
|
||||
* @extends {goog.iter.Iterator}
|
||||
* @param {goog.date.DateRange} dateRange The date range to iterate.
|
||||
*/
|
||||
goog.date.DateRange.Iterator = function(dateRange) {
|
||||
/**
|
||||
* The next date.
|
||||
* @type {goog.date.Date}
|
||||
* @private
|
||||
*/
|
||||
this.nextDate_ = dateRange.getStartDate().clone();
|
||||
|
||||
/**
|
||||
* The end date, expressed as an integer: YYYYMMDD.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.endDate_ = Number(dateRange.getEndDate().toIsoString());
|
||||
};
|
||||
goog.inherits(goog.date.DateRange.Iterator, goog.iter.Iterator);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.DateRange.Iterator.prototype.next = function() {
|
||||
if (Number(this.nextDate_.toIsoString()) > this.endDate_) {
|
||||
throw goog.iter.StopIteration;
|
||||
}
|
||||
|
||||
var rv = this.nextDate_.clone();
|
||||
this.nextDate_.add(new goog.date.Interval(goog.date.Interval.DAYS, 1));
|
||||
return rv;
|
||||
};
|
||||
@@ -0,0 +1,430 @@
|
||||
// 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 Functions for formatting relative dates. Such as "3 days ago"
|
||||
* "3 hours ago", "14 minutes ago", "12 days ago", "Today", "Yesterday".
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.date.relative');
|
||||
|
||||
goog.require('goog.i18n.DateTimeFormat');
|
||||
|
||||
|
||||
/**
|
||||
* Number of milliseconds in a minute.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.MINUTE_MS_ = 60000;
|
||||
|
||||
|
||||
/**
|
||||
* Number of milliseconds in a day.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.DAY_MS_ = 86400000;
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration used to identify time units internally.
|
||||
* @enum {number}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.Unit_ = {
|
||||
MINUTES: 0,
|
||||
HOURS: 1,
|
||||
DAYS: 2
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Full date formatter.
|
||||
* @type {goog.i18n.DateTimeFormat}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.fullDateFormatter_;
|
||||
|
||||
|
||||
/**
|
||||
* Short time formatter.
|
||||
* @type {goog.i18n.DateTimeFormat}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.shortTimeFormatter_;
|
||||
|
||||
|
||||
/**
|
||||
* Month-date formatter.
|
||||
* @type {goog.i18n.DateTimeFormat}
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.monthDateFormatter_;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a date in month format, e.g. Mar 15.
|
||||
* @param {Date} date The date object.
|
||||
* @return {string} The formatted string.
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.formatMonth_ = function(date) {
|
||||
if (!goog.date.relative.monthDateFormatter_) {
|
||||
goog.date.relative.monthDateFormatter_ =
|
||||
new goog.i18n.DateTimeFormat('MMM dd');
|
||||
}
|
||||
return goog.date.relative.monthDateFormatter_.format(date);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a date in short-time format, e.g. 2:50 PM.
|
||||
* @param {Date|goog.date.DateTime} date The date object.
|
||||
* @return {string} The formatted string.
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.formatShortTime_ = function(date) {
|
||||
if (!goog.date.relative.shortTimeFormatter_) {
|
||||
goog.date.relative.shortTimeFormatter_ = new goog.i18n.DateTimeFormat(
|
||||
goog.i18n.DateTimeFormat.Format.SHORT_TIME);
|
||||
}
|
||||
return goog.date.relative.shortTimeFormatter_.format(date);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a date in full date format, e.g. Tuesday, March 24, 2009.
|
||||
* @param {Date|goog.date.DateTime} date The date object.
|
||||
* @return {string} The formatted string.
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.formatFullDate_ = function(date) {
|
||||
if (!goog.date.relative.fullDateFormatter_) {
|
||||
goog.date.relative.fullDateFormatter_ = new goog.i18n.DateTimeFormat(
|
||||
goog.i18n.DateTimeFormat.Format.FULL_DATE);
|
||||
}
|
||||
return goog.date.relative.fullDateFormatter_.format(date);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a timestamp in milliseconds and outputs a relative time in the form
|
||||
* of "1 hour ago", "1 day ago", "in 1 hour", "in 2 days" etc. If the date
|
||||
* delta is over 2 weeks, then the output string will be empty.
|
||||
* @param {number} dateMs Date in milliseconds.
|
||||
* @return {string} The formatted date.
|
||||
*/
|
||||
goog.date.relative.format = function(dateMs) {
|
||||
|
||||
var now = goog.now();
|
||||
var delta = Math.floor((now - dateMs) / goog.date.relative.MINUTE_MS_);
|
||||
|
||||
var future = false;
|
||||
|
||||
if (delta < 0) {
|
||||
future = true;
|
||||
delta *= -1;
|
||||
}
|
||||
|
||||
if (delta < 60) { // Minutes.
|
||||
return goog.date.relative.getMessage_(
|
||||
delta, future, goog.date.relative.Unit_.MINUTES);
|
||||
|
||||
} else {
|
||||
delta = Math.floor(delta / 60);
|
||||
if (delta < 24) { // Hours.
|
||||
return goog.date.relative.getMessage_(
|
||||
delta, future, goog.date.relative.Unit_.HOURS);
|
||||
|
||||
} else {
|
||||
// We can be more than 24 hours apart but still only 1 day apart, so we
|
||||
// compare the closest time from today against the target time to find
|
||||
// the number of days in the delta.
|
||||
var midnight = new Date(goog.now());
|
||||
midnight.setHours(0);
|
||||
midnight.setMinutes(0);
|
||||
midnight.setSeconds(0);
|
||||
midnight.setMilliseconds(0);
|
||||
|
||||
// Convert to days ago.
|
||||
delta = Math.ceil(
|
||||
(midnight.getTime() - dateMs) / goog.date.relative.DAY_MS_);
|
||||
|
||||
if (future) {
|
||||
delta *= -1;
|
||||
}
|
||||
|
||||
// Uses days for less than 2-weeks.
|
||||
if (delta < 14) {
|
||||
return goog.date.relative.getMessage_(
|
||||
delta, future, goog.date.relative.Unit_.DAYS);
|
||||
|
||||
} else {
|
||||
// For messages older than 2 weeks do not show anything. The client
|
||||
// should decide the date format to show.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a timestamp in milliseconds and outputs a relative time in the form
|
||||
* of "1 hour ago", "1 day ago". All future times will be returned as 0 minutes
|
||||
* ago.
|
||||
*
|
||||
* This is provided for compatibility with users of the previous incarnation of
|
||||
* the above {@see #format} method who relied on it protecting against
|
||||
* future dates.
|
||||
*
|
||||
* @param {number} dateMs Date in milliseconds.
|
||||
* @return {string} The formatted date.
|
||||
*/
|
||||
goog.date.relative.formatPast = function(dateMs) {
|
||||
var now = goog.now();
|
||||
if (now < dateMs) {
|
||||
dateMs = now;
|
||||
}
|
||||
return goog.date.relative.format(dateMs);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Accepts a timestamp in milliseconds and outputs a relative day. i.e. "Today",
|
||||
* "Yesterday" or "Sept 15".
|
||||
*
|
||||
* @param {number} dateMs Date in milliseconds.
|
||||
* @return {string} The formatted date.
|
||||
*/
|
||||
goog.date.relative.formatDay = function(dateMs) {
|
||||
var message;
|
||||
var today = new Date(goog.now());
|
||||
|
||||
today.setHours(0);
|
||||
today.setMinutes(0);
|
||||
today.setSeconds(0);
|
||||
today.setMilliseconds(0);
|
||||
|
||||
var yesterday = new Date(today.getTime() - goog.date.relative.DAY_MS_);
|
||||
if (today.getTime() < dateMs) {
|
||||
/** @desc Today. */
|
||||
var MSG_TODAY = goog.getMsg('Today');
|
||||
message = MSG_TODAY;
|
||||
} else if (yesterday.getTime() < dateMs) {
|
||||
/** @desc Yesterday. */
|
||||
var MSG_YESTERDAY = goog.getMsg('Yesterday');
|
||||
message = MSG_YESTERDAY;
|
||||
} else {
|
||||
message = goog.date.relative.formatMonth_(new Date(dateMs));
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a date, adding the relative date in parenthesis. If the date is less
|
||||
* than 24 hours then the time will be printed, otherwise the full-date will be
|
||||
* used. Examples:
|
||||
* 2:20 PM (1 minute ago)
|
||||
* Monday, February 27, 2009 (4 days ago)
|
||||
* Tuesday, March 20, 2005 // Too long ago for a relative date.
|
||||
*
|
||||
* @param {Date|goog.date.DateTime} date A date object.
|
||||
* @param {string=} opt_shortTimeMsg An optional short time message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @param {string=} opt_fullDateMsg An optional date message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @return {string} The date string in the above form.
|
||||
*/
|
||||
goog.date.relative.getDateString = function(
|
||||
date, opt_shortTimeMsg, opt_fullDateMsg) {
|
||||
return goog.date.relative.getDateString_(
|
||||
date, goog.date.relative.format, opt_shortTimeMsg, opt_fullDateMsg);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a date, adding the relative date in parenthesis. Functions the same
|
||||
* as #getDateString but ensures that the date is always seen to be in the past.
|
||||
* If the date is in the future, it will be shown as 0 minutes ago.
|
||||
*
|
||||
* This is provided for compatibility with users of the previous incarnation of
|
||||
* the above {@see #getDateString} method who relied on it protecting against
|
||||
* future dates.
|
||||
*
|
||||
* @param {Date|goog.date.DateTime} date A date object.
|
||||
* @param {string=} opt_shortTimeMsg An optional short time message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @param {string=} opt_fullDateMsg An optional date message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @return {string} The date string in the above form.
|
||||
*/
|
||||
goog.date.relative.getPastDateString = function(
|
||||
date, opt_shortTimeMsg, opt_fullDateMsg) {
|
||||
return goog.date.relative.getDateString_(
|
||||
date, goog.date.relative.formatPast, opt_shortTimeMsg, opt_fullDateMsg);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Formats a date, adding the relative date in parenthesis. If the date is less
|
||||
* than 24 hours then the time will be printed, otherwise the full-date will be
|
||||
* used. Examples:
|
||||
* 2:20 PM (1 minute ago)
|
||||
* Monday, February 27, 2009 (4 days ago)
|
||||
* Tuesday, March 20, 2005 // Too long ago for a relative date.
|
||||
*
|
||||
* @param {Date|goog.date.DateTime} date A date object.
|
||||
* @param {function(number) : string} relativeFormatter Function to use when
|
||||
* formatting the relative date.
|
||||
* @param {string=} opt_shortTimeMsg An optional short time message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @param {string=} opt_fullDateMsg An optional date message can be
|
||||
* provided if available, so that it's not recalculated in this function.
|
||||
* @return {string} The date string in the above form.
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.getDateString_ = function(
|
||||
date, relativeFormatter, opt_shortTimeMsg, opt_fullDateMsg) {
|
||||
var dateMs = date.getTime();
|
||||
|
||||
var relativeDate = relativeFormatter(dateMs);
|
||||
|
||||
if (relativeDate) {
|
||||
relativeDate = ' (' + relativeDate + ')';
|
||||
}
|
||||
|
||||
var delta = Math.floor((goog.now() - dateMs) / goog.date.relative.MINUTE_MS_);
|
||||
if (delta < 60 * 24) {
|
||||
// TODO(user): this call raises an exception if date is a goog.date.Date.
|
||||
return (opt_shortTimeMsg || goog.date.relative.formatShortTime_(date)) +
|
||||
relativeDate;
|
||||
} else {
|
||||
return (opt_fullDateMsg || goog.date.relative.formatFullDate_(date)) +
|
||||
relativeDate;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a localized relative date string for a given delta and unit.
|
||||
* @param {number} delta Number of minutes/hours/days.
|
||||
* @param {boolean} future Whether the delta is in the future.
|
||||
* @param {goog.date.relative.Unit_} unit The units the delta is in.
|
||||
* @return {string} The message.
|
||||
* @private
|
||||
*/
|
||||
goog.date.relative.getMessage_ = function(delta, future, unit) {
|
||||
if (!future && unit == goog.date.relative.Unit_.MINUTES) {
|
||||
/**
|
||||
* @desc Relative date indicating how many minutes ago something happened
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_MINUTES_AGO_SINGULAR =
|
||||
goog.getMsg('{$num} minute ago', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating how many minutes ago something happened
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_MINUTES_AGO_PLURAL =
|
||||
goog.getMsg('{$num} minutes ago', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_MINUTES_AGO_SINGULAR : MSG_MINUTES_AGO_PLURAL;
|
||||
|
||||
} else if (future && unit == goog.date.relative.Unit_.MINUTES) {
|
||||
/**
|
||||
* @desc Relative date indicating in how many minutes something happens
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_IN_MINUTES_SINGULAR =
|
||||
goog.getMsg('in {$num} minute', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating in how many minutes something happens
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_IN_MINUTES_PLURAL =
|
||||
goog.getMsg('in {$num} minutes', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_IN_MINUTES_SINGULAR : MSG_IN_MINUTES_PLURAL;
|
||||
|
||||
} else if (!future && unit == goog.date.relative.Unit_.HOURS) {
|
||||
/**
|
||||
* @desc Relative date indicating how many hours ago something happened
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_HOURS_AGO_SINGULAR =
|
||||
goog.getMsg('{$num} hour ago', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating how many hours ago something happened
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_HOURS_AGO_PLURAL = goog.getMsg('{$num} hours ago', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_HOURS_AGO_SINGULAR : MSG_HOURS_AGO_PLURAL;
|
||||
|
||||
} else if (future && unit == goog.date.relative.Unit_.HOURS) {
|
||||
/**
|
||||
* @desc Relative date indicating in how many hours something happens
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_IN_HOURS_SINGULAR = goog.getMsg('in {$num} hour', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating in how many hours something happens
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_IN_HOURS_PLURAL = goog.getMsg('in {$num} hours', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_IN_HOURS_SINGULAR : MSG_IN_HOURS_PLURAL;
|
||||
|
||||
} else if (!future && unit == goog.date.relative.Unit_.DAYS) {
|
||||
/**
|
||||
* @desc Relative date indicating how many days ago something happened
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_DAYS_AGO_SINGULAR = goog.getMsg('{$num} day ago', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating how many days ago something happened
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_DAYS_AGO_PLURAL = goog.getMsg('{$num} days ago', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_DAYS_AGO_SINGULAR : MSG_DAYS_AGO_PLURAL;
|
||||
|
||||
} else if (future && unit == goog.date.relative.Unit_.DAYS) {
|
||||
/**
|
||||
* @desc Relative date indicating in how many days something happens
|
||||
* (singular).
|
||||
*/
|
||||
var MSG_IN_DAYS_SINGULAR = goog.getMsg('in {$num} day', {'num' : delta});
|
||||
|
||||
/**
|
||||
* @desc Relative date indicating in how many days something happens
|
||||
* (plural).
|
||||
*/
|
||||
var MSG_IN_DAYS_PLURAL = goog.getMsg('in {$num} days', {'num' : delta});
|
||||
|
||||
return delta == 1 ? MSG_IN_DAYS_SINGULAR : MSG_IN_DAYS_PLURAL;
|
||||
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
// 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 Locale independent date/time class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.date.UtcDateTime');
|
||||
|
||||
goog.require('goog.date');
|
||||
goog.require('goog.date.Date');
|
||||
goog.require('goog.date.DateTime');
|
||||
goog.require('goog.date.Interval');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class representing a date/time in GMT+0 time zone, without daylight saving.
|
||||
* Defaults to current date and time if none is specified. The get... and the
|
||||
* getUTC... methods are equivalent.
|
||||
*
|
||||
* @param {number|Object=} opt_year Four digit UTC year or a date-like object.
|
||||
* If not set, the created object will contain the date determined by
|
||||
* goog.now().
|
||||
* @param {number=} opt_month UTC month, 0 = Jan, 11 = Dec.
|
||||
* @param {number=} opt_date UTC date of month, 1 - 31.
|
||||
* @param {number=} opt_hours UTC hours, 0 - 23.
|
||||
* @param {number=} opt_minutes UTC minutes, 0 - 59.
|
||||
* @param {number=} opt_seconds UTC seconds, 0 - 59.
|
||||
* @param {number=} opt_milliseconds UTC milliseconds, 0 - 999.
|
||||
* @constructor
|
||||
* @extends {goog.date.DateTime}
|
||||
*/
|
||||
goog.date.UtcDateTime = function(opt_year, opt_month, opt_date, opt_hours,
|
||||
opt_minutes, opt_seconds, opt_milliseconds) {
|
||||
var timestamp;
|
||||
if (goog.isNumber(opt_year)) {
|
||||
timestamp = Date.UTC(opt_year, opt_month || 0, opt_date || 1,
|
||||
opt_hours || 0, opt_minutes || 0, opt_seconds || 0,
|
||||
opt_milliseconds || 0);
|
||||
} else {
|
||||
timestamp = opt_year ? opt_year.getTime() : goog.now();
|
||||
}
|
||||
this.date_ = new Date(timestamp);
|
||||
};
|
||||
goog.inherits(goog.date.UtcDateTime, goog.date.DateTime);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DateTime from a UTC datetime string expressed in ISO 8601 format.
|
||||
*
|
||||
* @param {string} formatted A date or datetime expressed in ISO 8601 format.
|
||||
* @return {goog.date.UtcDateTime} Parsed date or null if parse fails.
|
||||
*/
|
||||
goog.date.UtcDateTime.fromIsoString = function(formatted) {
|
||||
var ret = new goog.date.UtcDateTime(2000);
|
||||
return goog.date.setIso8601DateTime(ret, formatted) ? ret : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clones the UtcDateTime object.
|
||||
*
|
||||
* @return {!goog.date.UtcDateTime} A clone of the datetime object.
|
||||
* @override
|
||||
*/
|
||||
goog.date.UtcDateTime.prototype.clone = function() {
|
||||
var date = new goog.date.UtcDateTime(this.date_);
|
||||
date.setFirstDayOfWeek(this.getFirstDayOfWeek());
|
||||
date.setFirstWeekCutOffDay(this.getFirstWeekCutOffDay());
|
||||
return date;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.add = function(interval) {
|
||||
if (interval.years || interval.months) {
|
||||
var yearsMonths = new goog.date.Interval(interval.years, interval.months);
|
||||
goog.date.Date.prototype.add.call(this, yearsMonths);
|
||||
}
|
||||
var daysAndTimeMillis = 1000 * (
|
||||
interval.seconds + 60 * (
|
||||
interval.minutes + 60 * (
|
||||
interval.hours + 24 * interval.days)));
|
||||
this.date_ = new Date(this.date_.getTime() + daysAndTimeMillis);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getTimezoneOffset = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getFullYear =
|
||||
goog.date.DateTime.prototype.getUTCFullYear;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getMonth =
|
||||
goog.date.DateTime.prototype.getUTCMonth;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getDate =
|
||||
goog.date.DateTime.prototype.getUTCDate;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getHours =
|
||||
goog.date.DateTime.prototype.getUTCHours;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getMinutes =
|
||||
goog.date.DateTime.prototype.getUTCMinutes;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getSeconds =
|
||||
goog.date.DateTime.prototype.getUTCSeconds;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getMilliseconds =
|
||||
goog.date.DateTime.prototype.getUTCMilliseconds;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.getDay =
|
||||
goog.date.DateTime.prototype.getUTCDay;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setFullYear =
|
||||
goog.date.DateTime.prototype.setUTCFullYear;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setMonth =
|
||||
goog.date.DateTime.prototype.setUTCMonth;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setDate =
|
||||
goog.date.DateTime.prototype.setUTCDate;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setHours =
|
||||
goog.date.DateTime.prototype.setUTCHours;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setMinutes =
|
||||
goog.date.DateTime.prototype.setUTCMinutes;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setSeconds =
|
||||
goog.date.DateTime.prototype.setUTCSeconds;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.date.UtcDateTime.prototype.setMilliseconds =
|
||||
goog.date.DateTime.prototype.setUTCMilliseconds;
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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 Wrapper for a IndexedDB cursor.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.Cursor');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new IDBCursor wrapper object. Should not be created directly,
|
||||
* access cursor through object store.
|
||||
* @see goog.db.ObjectStore#openCursor
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.db.Cursor = function() {
|
||||
goog.base(this);
|
||||
};
|
||||
goog.inherits(goog.db.Cursor, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Underlying IndexedDB cursor object.
|
||||
*
|
||||
* @type {IDBCursor}
|
||||
* @private
|
||||
*/
|
||||
goog.db.Cursor.prototype.cursor_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Advances the cursor to the next position along its direction. When new data
|
||||
* is availible, the NEW_DATA event will be fired. If the cursor has reached the
|
||||
* end of the range it will fire the COMPLETE event. If opt_key is specified it
|
||||
* will advance to the key it matches in its direction.
|
||||
*
|
||||
* This wraps the native #continue method on the underlying object.
|
||||
*
|
||||
* @param {!Object=} opt_key The optional key to advance to.
|
||||
*/
|
||||
goog.db.Cursor.prototype.next = function(opt_key) {
|
||||
if (opt_key) {
|
||||
this.cursor_['continue'](opt_key);
|
||||
} else {
|
||||
this.cursor_['continue']();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates the value at the current position of the cursor in the object store.
|
||||
* If the cursor points to a value that has just been deleted, a new value is
|
||||
* created.
|
||||
*
|
||||
* @param {!Object} value The value to be stored.
|
||||
* @return {!goog.async.Deferred} The resulting deferred request.
|
||||
*/
|
||||
goog.db.Cursor.prototype.update = function(value) {
|
||||
var msg = 'updating via cursor with value ';
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
|
||||
try {
|
||||
request = this.cursor_.update(value);
|
||||
} catch (err) {
|
||||
msg += goog.debug.deepExpose(value);
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback();
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
msg += goog.debug.deepExpose(value);
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode, msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the value at the cursor's position, without changing the cursor's
|
||||
* position. Once the value is deleted, the cursor's value is set to null.
|
||||
*
|
||||
* @return {!goog.async.Deferred} The resulting deferred request.
|
||||
*/
|
||||
goog.db.Cursor.prototype.remove = function() {
|
||||
var msg = 'deleting via cursor';
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
|
||||
try {
|
||||
request = this.cursor_['delete']();
|
||||
} catch (err) {
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback();
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode, msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} The value for the value at the cursor's position. Undefined
|
||||
* if no current value, or null if value has just been deleted.
|
||||
*/
|
||||
goog.db.Cursor.prototype.getValue = function() {
|
||||
return this.cursor_['value'];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {*} The key for the value at the cursor's position. If the
|
||||
* cursor is outside its range, this is undefined.
|
||||
*/
|
||||
goog.db.Cursor.prototype.getKey = function() {
|
||||
return this.cursor_.key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Possible cursor directions.
|
||||
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBCursor
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.db.Cursor.Direction = {
|
||||
NEXT: 0,
|
||||
NEXT_NO_DUPLICATE: 1,
|
||||
PREV: 2,
|
||||
PREV_NO_DUPLICATE: 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event types that the cursor can dispatch. COMPLETE events are dispatched when
|
||||
* a cursor is depleted of values, a NEW_DATA event if there is new data
|
||||
* availible, and ERROR if an error occurred.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.db.Cursor.EventType = {
|
||||
COMPLETE: 'c',
|
||||
ERROR: 'e',
|
||||
NEW_DATA: 'n'
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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 Wrappers for the HTML5 IndexedDB. The wrappers export nearly
|
||||
* the same interface as the standard API, but return goog.async.Deferred
|
||||
* objects instead of request objects and use Closure events. The wrapper works
|
||||
* and has been tested on Chrome version 18+. Though they should work in theory,
|
||||
* the wrapper tests fail in strange, non-deterministic ways on Firefox 6,
|
||||
* unfortunately.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <code>
|
||||
* goog.db.openDatabase('mydb').addCallback(function(db) {
|
||||
* return db.setVersion('1.0').addCallback(function(tx) {
|
||||
* db.createObjectStore('mystore');
|
||||
* // restart to see our structure changes
|
||||
* return goog.db.openDatabase('mydb');
|
||||
* });
|
||||
* }).addCallback(function(db) {
|
||||
* var putTx = db.createTransaction(
|
||||
* [],
|
||||
* goog.db.Transaction.TransactionMode.READ_WRITE);
|
||||
* var store = putTx.objectStore('mystore');
|
||||
* store.put('value', 'key');
|
||||
* goog.listen(putTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
|
||||
* var getTx = db.createTransaction([]);
|
||||
* var request = getTx.objectStore('mystore').get('key');
|
||||
* request.addCallback(function(result) {
|
||||
* ...
|
||||
* });
|
||||
* });
|
||||
* </code>
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.db.IndexedDb');
|
||||
|
||||
|
||||
/**
|
||||
* Opens a database connection and wraps it.
|
||||
*
|
||||
* @param {string} name The name of the database to open.
|
||||
* @return {!goog.async.Deferred} The deferred database object.
|
||||
*/
|
||||
goog.db.openDatabase = function(name) {
|
||||
var indexedDb = goog.global.indexedDB || goog.global.mozIndexedDB ||
|
||||
goog.global.webkitIndexedDB || goog.global.moz_indexedDB;
|
||||
|
||||
var d = new goog.async.Deferred();
|
||||
var openRequest = indexedDb.open(name);
|
||||
openRequest.onsuccess = function(ev) {
|
||||
var db = new goog.db.IndexedDb(ev.target.result);
|
||||
d.callback(db);
|
||||
};
|
||||
openRequest.onerror = function(ev) {
|
||||
var msg = 'opening database ' + name;
|
||||
d.errback(new goog.db.Error(ev.target.code, msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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 Error classes for the IndexedDB wrapper.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.Error');
|
||||
goog.provide('goog.db.Error.ErrorCode');
|
||||
goog.provide('goog.db.Error.VersionChangeBlockedError');
|
||||
|
||||
goog.require('goog.debug.Error');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A database error. Since the stack trace can be unhelpful in an asynchronous
|
||||
* context, the error provides a message about where it was produced.
|
||||
*
|
||||
* @param {number} code The error code.
|
||||
* @param {string} context A description of where the error occured.
|
||||
* @param {string=} opt_message Additional message.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.db.Error = function(code, context, opt_message) {
|
||||
var msg = 'Error ' + context + ': ' + goog.db.Error.getMessage(code);
|
||||
if (opt_message) {
|
||||
msg += ', ' + opt_message;
|
||||
}
|
||||
goog.base(this, msg);
|
||||
|
||||
/**
|
||||
* The code for this error.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
this.code = code;
|
||||
};
|
||||
goog.inherits(goog.db.Error, goog.debug.Error);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A specific kind of database error. If a Version Change is unable to proceed
|
||||
* due to other open database connections, it will block and this error will be
|
||||
* thrown.
|
||||
*
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.db.Error.VersionChangeBlockedError = function() {
|
||||
goog.base(this, 'Version change blocked');
|
||||
};
|
||||
goog.inherits(goog.db.Error.VersionChangeBlockedError, goog.debug.Error);
|
||||
|
||||
|
||||
/**
|
||||
* Synthetic error codes for database errors, for use when IndexedDB
|
||||
* support is not available. This numbering differs in practice
|
||||
* from the browser implementations, but it is not meant to be reliable:
|
||||
* this object merely ensures that goog.db.Error is loadable on platforms
|
||||
* that do not support IndexedDB.
|
||||
*
|
||||
* @enum {number}
|
||||
* @private
|
||||
*/
|
||||
goog.db.Error.DatabaseErrorCode_ = {
|
||||
UNKNOWN_ERR: 1,
|
||||
NON_TRANSIENT_ERR: 2,
|
||||
NOT_FOUND_ERR: 3,
|
||||
CONSTRAINT_ERR: 4,
|
||||
DATA_ERR: 5,
|
||||
NOT_ALLOWED_ERR: 6,
|
||||
TRANSACTION_INACTIVE_ERR: 7,
|
||||
ABORT_ERR: 8,
|
||||
READ_ONLY_ERR: 9,
|
||||
TRANSIENT_ERR: 11,
|
||||
TIMEOUT_ERR: 10,
|
||||
QUOTA_ERR: 11,
|
||||
INVALID_ACCESS_ERR: 12
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error codes for database errors.
|
||||
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBDatabaseException
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.db.Error.ErrorCode = {
|
||||
UNKNOWN_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).UNKNOWN_ERR,
|
||||
NON_TRANSIENT_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).NON_TRANSIENT_ERR,
|
||||
NOT_FOUND_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).NOT_FOUND_ERR,
|
||||
CONSTRAINT_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).CONSTRAINT_ERR,
|
||||
DATA_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).DATA_ERR,
|
||||
NOT_ALLOWED_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).NOT_ALLOWED_ERR,
|
||||
TRANSACTION_INACTIVE_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).TRANSACTION_INACTIVE_ERR,
|
||||
ABORT_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).ABORT_ERR,
|
||||
READ_ONLY_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).READ_ONLY_ERR,
|
||||
TIMEOUT_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).TIMEOUT_ERR,
|
||||
QUOTA_ERR: (goog.global.IDBDatabaseException ||
|
||||
goog.global.webkitIDBDatabaseException ||
|
||||
goog.db.Error.DatabaseErrorCode_).QUOTA_ERR,
|
||||
INVALID_ACCESS_ERR: (goog.global.DOMException ||
|
||||
goog.db.Error.DatabaseErrorCode_).INVALID_ACCESS_ERR
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Translates an error code into a more useful message.
|
||||
*
|
||||
* @param {number} code Error code.
|
||||
* @return {string} A debug message.
|
||||
*/
|
||||
goog.db.Error.getMessage = function(code) {
|
||||
switch (code) {
|
||||
case goog.db.Error.ErrorCode.UNKNOWN_ERR:
|
||||
return 'Unknown error';
|
||||
case goog.db.Error.ErrorCode.NON_TRANSIENT_ERR:
|
||||
return 'Invalid operation';
|
||||
case goog.db.Error.ErrorCode.NOT_FOUND_ERR:
|
||||
return 'Required database object not found';
|
||||
case goog.db.Error.ErrorCode.CONSTRAINT_ERR:
|
||||
return 'Constraint unsatisfied';
|
||||
case goog.db.Error.ErrorCode.DATA_ERR:
|
||||
return 'Invalid data';
|
||||
case goog.db.Error.ErrorCode.NOT_ALLOWED_ERR:
|
||||
return 'Operation disallowed';
|
||||
case goog.db.Error.ErrorCode.TRANSACTION_INACTIVE_ERR:
|
||||
return 'Transaction not active';
|
||||
case goog.db.Error.ErrorCode.ABORT_ERR:
|
||||
return 'Request aborted';
|
||||
case goog.db.Error.ErrorCode.READ_ONLY_ERR:
|
||||
return 'Modifying operation not allowed in a read-only transaction';
|
||||
case goog.db.Error.ErrorCode.TIMEOUT_ERR:
|
||||
return 'Transaction timed out';
|
||||
case goog.db.Error.ErrorCode.QUOTA_ERR:
|
||||
return 'Database storage space quota exceeded';
|
||||
case goog.db.Error.ErrorCode.INVALID_ACCESS_ERR:
|
||||
return 'Invalid operation';
|
||||
default:
|
||||
return 'Unrecognized exception with code ' + code;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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 Wrapper for an IndexedDB index.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.Index');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.debug');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an IDBIndex wrapper object. Indexes are associated with object
|
||||
* stores and provide methods for looking up objects based on their non-key
|
||||
* properties. Should not be created directly, access through the object store
|
||||
* it belongs to.
|
||||
* @see goog.db.ObjectStore#getIndex
|
||||
*
|
||||
* @param {!IDBIndex} index Underlying IDBIndex object.
|
||||
* @constructor
|
||||
*/
|
||||
goog.db.Index = function(index) {
|
||||
/**
|
||||
* Underlying IndexedDB index object.
|
||||
*
|
||||
* @type {!IDBIndex}
|
||||
* @private
|
||||
*/
|
||||
this.index_ = index;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} Name of the index.
|
||||
*/
|
||||
goog.db.Index.prototype.getName = function() {
|
||||
return this.index_.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} Key path of the index.
|
||||
*/
|
||||
goog.db.Index.prototype.getKeyPath = function() {
|
||||
return this.index_.keyPath;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the index enforces that there is only one object
|
||||
* for each unique value it indexes on.
|
||||
*/
|
||||
goog.db.Index.prototype.isUnique = function() {
|
||||
return this.index_.unique;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for get and getKey.
|
||||
*
|
||||
* @param {string} fn Function name to call on the index to get the request.
|
||||
* @param {string} msg Message to give to the error.
|
||||
* @param {!Object} value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} The resulting deferred object.
|
||||
* @private
|
||||
*/
|
||||
goog.db.Index.prototype.get_ = function(fn, msg, value) {
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
request = this.index_[fn](value);
|
||||
} catch (err) {
|
||||
msg += ' with value ' + goog.debug.deepExpose(value);
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback(ev.target.result);
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
msg += ' with value ' + goog.debug.deepExpose(value);
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fetches a single object from the object store. Even if there are multiple
|
||||
* objects that match the given value, this method will get only one of them.
|
||||
*
|
||||
* @param {!Object} value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} The deferred object that matches the value.
|
||||
*/
|
||||
goog.db.Index.prototype.get = function(value) {
|
||||
return this.get_('get', 'getting from index ' + this.getName(), value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Looks up a single object from the object store and gives back the key that
|
||||
* it's listed under in the object store. Even if there are multiple objects
|
||||
* that match the given value, this method will only get one of their keys.
|
||||
*
|
||||
* @param {!Object} value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} The deferred key for the object that matches
|
||||
* the value.
|
||||
*/
|
||||
goog.db.Index.prototype.getKey = function(value) {
|
||||
return this.get_('getKey', 'getting key from index ' + this.getName(), value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for getAll and getAllKeys.
|
||||
*
|
||||
* @param {string} fn Function name to call on the index to get the request.
|
||||
* @param {string} msg Message to give to the error.
|
||||
* @param {!Object=} opt_value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} The resulting deferred array of objects.
|
||||
* @private
|
||||
*/
|
||||
goog.db.Index.prototype.getAll_ = function(fn, msg, opt_value) {
|
||||
// This is the most common use of IDBKeyRange. If more specific uses of
|
||||
// cursors are needed then a full wrapper should be created.
|
||||
var IDBKeyRange = goog.global.IDBKeyRange || goog.global.webkitIDBKeyRange;
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
if (opt_value) {
|
||||
request = this.index_[fn](IDBKeyRange.bound(opt_value, opt_value));
|
||||
} else {
|
||||
request = this.index_[fn]();
|
||||
}
|
||||
} catch (err) {
|
||||
if (opt_value) {
|
||||
msg += ' for value ' + goog.debug.deepExpose(opt_value);
|
||||
}
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
var result = [];
|
||||
request.onsuccess = function(ev) {
|
||||
var cursor = ev.target.result;
|
||||
if (cursor) {
|
||||
result.push(cursor.value);
|
||||
cursor['continue']();
|
||||
} else {
|
||||
d.callback(result);
|
||||
}
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
if (opt_value) {
|
||||
msg += ' for value ' + goog.debug.deepExpose(opt_value);
|
||||
}
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all indexed objects. If the value is provided, gets all indexed objects
|
||||
* that match the value instead.
|
||||
*
|
||||
* @param {!Object=} opt_value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} A deferred array of objects that match the
|
||||
* value.
|
||||
*/
|
||||
goog.db.Index.prototype.getAll = function(opt_value) {
|
||||
return this.getAll_(
|
||||
'openCursor',
|
||||
'getting all from index ' + this.getName(),
|
||||
opt_value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the keys to look up all the indexed objects. If the value is provided,
|
||||
* gets all keys for objects that match the value instead.
|
||||
*
|
||||
* @param {!Object=} opt_value Value to look up in the index.
|
||||
* @return {!goog.async.Deferred} A deferred array of keys for objects that
|
||||
* match the value.
|
||||
*/
|
||||
goog.db.Index.prototype.getAllKeys = function(opt_value) {
|
||||
return this.getAll_(
|
||||
'openKeyCursor',
|
||||
'getting all keys from index ' + this.getName(),
|
||||
opt_value);
|
||||
};
|
||||
@@ -0,0 +1,195 @@
|
||||
// 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 Wrapper for an IndexedDB database.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.IndexedDb');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.db.Error.VersionChangeBlockedError');
|
||||
goog.require('goog.db.ObjectStore');
|
||||
goog.require('goog.db.Transaction');
|
||||
goog.require('goog.db.Transaction.TransactionMode');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an IDBDatabase wrapper object. The database object has methods for
|
||||
* setting the version to change the structure of the database and for creating
|
||||
* transactions to get or modify the stored records. Should not be created
|
||||
* directly, call {@link goog.db.openDatabase} to set up the connection.
|
||||
*
|
||||
* @param {!IDBDatabase} db Underlying IndexedDB database object.
|
||||
* @constructor
|
||||
*/
|
||||
goog.db.IndexedDb = function(db) {
|
||||
/**
|
||||
* Underlying IndexedDB database object.
|
||||
*
|
||||
* @type {!IDBDatabase}
|
||||
* @private
|
||||
*/
|
||||
this.db_ = db;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* True iff the database connection is open.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.open_ = true;
|
||||
|
||||
|
||||
/**
|
||||
* Closes the database connection. Metadata queries can still be made after this
|
||||
* method is called, but otherwise this wrapper should not be used further.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.close = function() {
|
||||
if (this.open_) {
|
||||
this.db_.close();
|
||||
this.open_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether a connection is open and the database can be used.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.isOpen = function() {
|
||||
return this.open_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The name of this database.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.getName = function() {
|
||||
return this.db_.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The current database version.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.getVersion = function() {
|
||||
return this.db_.version;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array} List of object stores in this database.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.getObjectStoreNames = function() {
|
||||
return this.db_.objectStoreNames;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an object store in this database. Can only be called inside the
|
||||
* callback for the Deferred returned from #setVersion.
|
||||
*
|
||||
* @param {string} name Name for the new object store.
|
||||
* @param {Object=} opt_params Options object. The available options are:
|
||||
* keyPath, which is a string and determines what object attribute
|
||||
* to use as the key when storing objects in this object store; and
|
||||
* autoIncrement, which is a boolean, which defaults to false and determines
|
||||
* whether the object store should automatically generate keys for stored
|
||||
* objects. If keyPath is not provided and autoIncrement is false, then all
|
||||
* insert operations must provide a key as a parameter.
|
||||
* @return {goog.db.ObjectStore} The newly created object store.
|
||||
* @throws {goog.db.Error} If there's a problem creating the object store.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.createObjectStore = function(name, opt_params) {
|
||||
try {
|
||||
return new goog.db.ObjectStore(this.db_.createObjectStore(
|
||||
name, opt_params));
|
||||
} catch (ex) {
|
||||
throw new goog.db.Error(ex.code, 'creating object store ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deletes an object store. Can only be called inside the callback for the
|
||||
* Deferred returned from #setVersion.
|
||||
*
|
||||
* @param {string} name Name of the object store to delete.
|
||||
* @throws {goog.db.Error} If there's a problem deleting the object store.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.deleteObjectStore = function(name) {
|
||||
try {
|
||||
this.db_.deleteObjectStore(name);
|
||||
} catch (ex) {
|
||||
throw new goog.db.Error(ex.code, 'deleting object store ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates the version of the database and returns a Deferred transaction.
|
||||
* The database's structure can be changed inside this Deferred's callback, but
|
||||
* nowhere else. This means adding or deleting object stores, and adding or
|
||||
* deleting indexes. The version change will not succeed unless there are no
|
||||
* other connections active for this database anywhere. A new database
|
||||
* connection should be opened after the version change is finished to pick
|
||||
* up changes.
|
||||
*
|
||||
* @param {string} version The new version of the database.
|
||||
* @return {!goog.async.Deferred} The deferred transaction for changing the
|
||||
* version.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.setVersion = function(version) {
|
||||
var d = new goog.async.Deferred();
|
||||
var request = this.db_.setVersion(version);
|
||||
request.onsuccess = function(ev) {
|
||||
// the transaction is in the result field (the transaction field is null
|
||||
// for version change requests)
|
||||
d.callback(new goog.db.Transaction(ev.target.result));
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
d.errback(new goog.db.Error(ev.target.errorCode, 'setting version'));
|
||||
};
|
||||
request.onblocked = function(ev) {
|
||||
d.errback(new goog.db.Error.VersionChangeBlockedError());
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new transaction.
|
||||
*
|
||||
* @param {!Array.<string>} storeNames A list of strings that contains the
|
||||
* transaction's scope, the object stores that this transaction can operate
|
||||
* on.
|
||||
* @param {goog.db.Transaction.TransactionMode=} opt_mode The mode of the
|
||||
* transaction. If not present, the default is READ_ONLY. For VERSION_CHANGE
|
||||
* transactions call {@link goog.db.IndexedDB#setVersion} instead.
|
||||
* @return {!goog.db.Transaction} The wrapper for the newly created transaction.
|
||||
* @throws {goog.db.Error} If there's a problem creating the transaction.
|
||||
*/
|
||||
goog.db.IndexedDb.prototype.createTransaction = function(storeNames, opt_mode) {
|
||||
try {
|
||||
return new goog.db.Transaction(this.db_.transaction(storeNames, opt_mode));
|
||||
} catch (err) {
|
||||
throw new goog.db.Error(err.code, 'creating transaction');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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 Wrapper for a IndexedDB key range.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.KeyRange');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new IDBKeyRange wrapper object. Should not be created directly,
|
||||
* instead use one of the static factory methods. For example:
|
||||
* @see goog.db.KeyRange.bound
|
||||
* @see goog.db.KeyRange.lowerBound
|
||||
*
|
||||
* @param {!IDBKeyRange} range Underlying IDBKeyRange object.
|
||||
* @constructor
|
||||
*/
|
||||
goog.db.KeyRange = function(range) {
|
||||
/**
|
||||
* Underlying IDBKeyRange object.
|
||||
*
|
||||
* @type {!IDBKeyRange}
|
||||
* @private
|
||||
*/
|
||||
this.range_ = range;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The IDBKeyRange.
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
goog.db.KeyRange.IDB_KEY_RANGE_ = goog.global.IDBKeyRange ||
|
||||
goog.global.webkitIDBKeyRange;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new key range for a single value.
|
||||
*
|
||||
* @param {Object} key The single value in the range.
|
||||
* @return {!goog.db.KeyRange} The key range.
|
||||
*/
|
||||
goog.db.KeyRange.only = function(key) {
|
||||
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.only(key));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a key range with upper and lower bounds.
|
||||
*
|
||||
* @param {Object} lower The value of the lower bound.
|
||||
* @param {Object} upper The value of the upper bound.
|
||||
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
|
||||
* value.
|
||||
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
|
||||
* value.
|
||||
* @return {!goog.db.KeyRange} The key range.
|
||||
*/
|
||||
goog.db.KeyRange.bound = function(lower, upper, opt_lowerOpen, opt_upperOpen) {
|
||||
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.bound(
|
||||
lower, upper, opt_lowerOpen, opt_upperOpen));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a key range with a lower bound only, finishes at the last record.
|
||||
*
|
||||
* @param {Object} lower The value of the lower bound.
|
||||
* @param {boolean=} opt_lowerOpen If true, the range excludes the lower bound
|
||||
* value.
|
||||
* @return {!goog.db.KeyRange} The key range.
|
||||
*/
|
||||
goog.db.KeyRange.lowerBound = function(lower, opt_lowerOpen) {
|
||||
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.lowerBound(
|
||||
lower, opt_lowerOpen));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a key range with a upper bound only, starts at the first record.
|
||||
*
|
||||
* @param {Object} upper The value of the upper bound.
|
||||
* @param {boolean=} opt_upperOpen If true, the range excludes the upper bound
|
||||
* value.
|
||||
* @return {!goog.db.KeyRange} The key range.
|
||||
*/
|
||||
goog.db.KeyRange.upperBound = function(upper, opt_upperOpen) {
|
||||
return new goog.db.KeyRange(goog.db.KeyRange.IDB_KEY_RANGE_.upperBound(
|
||||
upper, opt_upperOpen));
|
||||
};
|
||||
@@ -0,0 +1,401 @@
|
||||
// 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 Wrapper for an IndexedDB object store.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.ObjectStore');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.db.Cursor');
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.db.Index');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.events');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an IDBObjectStore wrapper object. Object stores have methods for
|
||||
* storing and retrieving records, and are accessed through a transaction
|
||||
* object. They also have methods for creating indexes associated with the
|
||||
* object store. They can only be created when setting the version of the
|
||||
* database. Should not be created directly, access object stores through
|
||||
* transactions.
|
||||
* @see goog.db.IndexedDb#setVersion
|
||||
* @see goog.db.Transaction#objectStore
|
||||
*
|
||||
* @param {!IDBObjectStore} store The backing IndexedDb object.
|
||||
* @constructor
|
||||
*/
|
||||
goog.db.ObjectStore = function(store) {
|
||||
/**
|
||||
* Underlying IndexedDB object store object.
|
||||
*
|
||||
* @type {!IDBObjectStore}
|
||||
* @private
|
||||
*/
|
||||
this.store_ = store;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The name of the object store.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.getName = function() {
|
||||
return this.store_.name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for put and add.
|
||||
*
|
||||
* @param {string} fn Function name to call on the object store.
|
||||
* @param {string} msg Message to give to the error.
|
||||
* @param {!Object} value Value to insert into the object store.
|
||||
* @param {!Object=} opt_key The key to use.
|
||||
* @return {!goog.async.Deferred} The resulting deferred request.
|
||||
* @private
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.insert_ = function(fn, msg, value, opt_key) {
|
||||
// TODO(user): refactor wrapping an IndexedDB request in a Deferred by
|
||||
// creating a higher-level abstraction for it (mostly affects here and
|
||||
// goog.db.Index)
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
// put or add with (value, undefined) throws an error, so we need to check
|
||||
// for undefined ourselves
|
||||
if (opt_key) {
|
||||
request = this.store_[fn](value, opt_key);
|
||||
} else {
|
||||
request = this.store_[fn](value);
|
||||
}
|
||||
} catch (err) {
|
||||
msg += goog.debug.deepExpose(value);
|
||||
if (opt_key) {
|
||||
msg += ', with key ' + goog.debug.deepExpose(opt_key);
|
||||
}
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback();
|
||||
};
|
||||
var self = this;
|
||||
request.onerror = function(ev) {
|
||||
msg += goog.debug.deepExpose(value);
|
||||
if (opt_key) {
|
||||
msg += ', with key ' + goog.debug.deepExpose(opt_key);
|
||||
}
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to the object store. Replaces existing objects with the
|
||||
* same key.
|
||||
*
|
||||
* @param {!Object} value The value to put.
|
||||
* @param {!Object=} opt_key The key to use. Cannot be used if the keyPath was
|
||||
* specified for the object store. If the keyPath was not specified but
|
||||
* autoIncrement was not enabled, it must be used.
|
||||
* @return {!goog.async.Deferred} The deferred put request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.put = function(value, opt_key) {
|
||||
return this.insert_(
|
||||
'put',
|
||||
'putting into ' + this.getName() + ' with value',
|
||||
value,
|
||||
opt_key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to the object store. Requires that there is no object with
|
||||
* the same key already present.
|
||||
*
|
||||
* @param {!Object} value The value to add.
|
||||
* @param {!Object=} opt_key The key to use. Cannot be used if the keyPath was
|
||||
* specified for the object store. If the keyPath was not specified but
|
||||
* autoIncrement was not enabled, it must be used.
|
||||
* @return {!goog.async.Deferred} The deferred add request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.add = function(value, opt_key) {
|
||||
return this.insert_(
|
||||
'add',
|
||||
'adding into ' + this.getName() + ' with value ',
|
||||
value,
|
||||
opt_key);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes an object from the store. No-op if there is no object present with
|
||||
* the given key.
|
||||
*
|
||||
* @param {!Object} key The key to remove objects under.
|
||||
* @return {!goog.async.Deferred} The deferred remove request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.remove = function(key) {
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
request = this.store_['delete'](key);
|
||||
} catch (err) {
|
||||
var msg = 'removing from ' + this.getName() + ' with key ' +
|
||||
goog.debug.deepExpose(key);
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback();
|
||||
};
|
||||
var self = this;
|
||||
request.onerror = function(ev) {
|
||||
var msg = 'removing from ' + self.getName() + ' with key ' +
|
||||
goog.debug.deepExpose(key);
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets an object from the store. If no object is present with that key
|
||||
* the result is {@code undefined}.
|
||||
*
|
||||
* @param {!Object} key The key to look up.
|
||||
* @return {!goog.async.Deferred} The deferred get request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.get = function(key) {
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
request = this.store_.get(key);
|
||||
} catch (err) {
|
||||
var msg = 'getting from ' + this.getName() + ' with key ' +
|
||||
goog.debug.deepExpose(key);
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback(ev.target.result);
|
||||
};
|
||||
var self = this;
|
||||
request.onerror = function(ev) {
|
||||
var msg = 'getting from ' + self.getName() + ' with key ' +
|
||||
goog.debug.deepExpose(key);
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all objects from the store and returns them as an array.
|
||||
*
|
||||
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
|
||||
* over the whole object store.
|
||||
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
|
||||
* moves in a forward direction with duplicates.
|
||||
* @return {!goog.async.Deferred} The deferred getAll request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.getAll = function(opt_range, opt_direction) {
|
||||
var d = new goog.async.Deferred();
|
||||
var cursor;
|
||||
try {
|
||||
cursor = this.openCursor(opt_range, opt_direction);
|
||||
} catch (err) {
|
||||
d.errback(err);
|
||||
return d;
|
||||
}
|
||||
|
||||
var result = [];
|
||||
var key = goog.events.listen(
|
||||
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
|
||||
result.push(cursor.getValue());
|
||||
cursor.next();
|
||||
});
|
||||
|
||||
goog.events.listenOnce(cursor, [
|
||||
goog.db.Cursor.EventType.ERROR,
|
||||
goog.db.Cursor.EventType.COMPLETE
|
||||
], function(evt) {
|
||||
goog.events.unlistenByKey(key);
|
||||
if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
|
||||
d.callback(result);
|
||||
} else {
|
||||
d.errback();
|
||||
}
|
||||
});
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens a cursor over the specified key range. Returns a cursor object which is
|
||||
* able to iterate over the given range.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <code>
|
||||
* var cursor = objectStore.openCursor(goog.db.Range.bound('a', 'c'));
|
||||
*
|
||||
* var key = goog.events.listen(
|
||||
* cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
|
||||
* // Do something with data.
|
||||
* cursor.next();
|
||||
* });
|
||||
*
|
||||
* goog.events.listenOnce(
|
||||
* cursor, goog.db.Cursor.EventType.COMPLETE, function() {
|
||||
* // Clean up listener, and perform a finishing operation on the data.
|
||||
* goog.events.unlistenByKey(key);
|
||||
* });
|
||||
* </code>
|
||||
*
|
||||
* @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
|
||||
* over the whole object store.
|
||||
* @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
|
||||
* moves in a forward direction with duplicates.
|
||||
* @return {!goog.db.Cursor} The cursor.
|
||||
* @throws {goog.db.Error} If there was a problem opening the cursor.
|
||||
* @suppress {accessControls}
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.openCursor = function(opt_range, opt_direction) {
|
||||
var msg = 'opening cursor ' + this.getName();
|
||||
var cursor = new goog.db.Cursor();
|
||||
var request;
|
||||
|
||||
try {
|
||||
var range = opt_range ? opt_range.range_ : null;
|
||||
if (opt_direction) {
|
||||
request = this.store_.openCursor(range, opt_direction);
|
||||
} else {
|
||||
request = this.store_.openCursor(range);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new goog.db.Error(err.code, msg);
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
cursor.cursor_ = ev.target.result || null;
|
||||
if (cursor.cursor_) {
|
||||
cursor.dispatchEvent(goog.db.Cursor.EventType.NEW_DATA);
|
||||
} else {
|
||||
cursor.dispatchEvent(goog.db.Cursor.EventType.COMPLETE);
|
||||
}
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
cursor.dispatchEvent(goog.db.Cursor.EventType.ERROR);
|
||||
};
|
||||
return cursor;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deletes all objects from the store.
|
||||
*
|
||||
* @return {!goog.async.Deferred} The deferred clear request.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.clear = function() {
|
||||
var msg = 'clearing store ' + this.getName();
|
||||
var d = new goog.async.Deferred();
|
||||
var request;
|
||||
try {
|
||||
request = this.store_.clear();
|
||||
} catch (err) {
|
||||
d.errback(new goog.db.Error(err.code, msg));
|
||||
return d;
|
||||
}
|
||||
request.onsuccess = function(ev) {
|
||||
d.callback();
|
||||
};
|
||||
request.onerror = function(ev) {
|
||||
d.errback(new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
msg));
|
||||
};
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an index in this object store. Can only be called inside the callback
|
||||
* for the Deferred returned from goog.db.IndexedDb#setVersion.
|
||||
*
|
||||
* @param {string} name Name of the index to create.
|
||||
* @param {string} keyPath Attribute to index on.
|
||||
* @param {!Object=} opt_parameters Optional parameters object. The only
|
||||
* available option is unique, which defaults to false. If unique is true,
|
||||
* the index will enforce that there is only ever one object in the object
|
||||
* store for each unique value it indexes on.
|
||||
* @return {goog.db.Index} The newly created, wrapped index.
|
||||
* @throws {goog.db.Error} In case of an error creating the index.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.createIndex = function(
|
||||
name, keyPath, opt_parameters) {
|
||||
try {
|
||||
return new goog.db.Index(this.store_.createIndex(
|
||||
name, keyPath, opt_parameters));
|
||||
} catch (err) {
|
||||
var msg = 'creating new index ' + name + ' with key path ' + keyPath;
|
||||
throw new goog.db.Error(err.code, msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets an index.
|
||||
*
|
||||
* @param {string} name Name of the index to fetch.
|
||||
* @return {goog.db.Index} The requested wrapped index.
|
||||
* @throws {goog.db.Error} In case of an error getting the index.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.getIndex = function(name) {
|
||||
try {
|
||||
return new goog.db.Index(this.store_.index(name));
|
||||
} catch (err) {
|
||||
var msg = 'getting index ' + name;
|
||||
throw new goog.db.Error(err.code, msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deletes an index from the object store. Can only be called inside the
|
||||
* callback for the Deferred returned from goog.db.IndexedDb#setVersion.
|
||||
*
|
||||
* @param {string} name Name of the index to delete.
|
||||
* @throws {goog.db.Error} In case of an error deleting the index.
|
||||
*/
|
||||
goog.db.ObjectStore.prototype.deleteIndex = function(name) {
|
||||
try {
|
||||
this.store_.deleteIndex(name);
|
||||
} catch (err) {
|
||||
var msg = 'deleting index ' + name;
|
||||
throw new goog.db.Error(err.code, msg);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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 Wrapper for an IndexedDB transaction.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.db.Transaction');
|
||||
goog.provide('goog.db.Transaction.TransactionMode');
|
||||
|
||||
goog.require('goog.db.Error');
|
||||
goog.require('goog.db.ObjectStore');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new transaction. Transactions contain methods for accessing object
|
||||
* stores and are created from the database object. Should not be created
|
||||
* directly, open a database and call createTransaction on it.
|
||||
* @see goog.db.IndexedDb#createTransaction
|
||||
*
|
||||
* @param {!IDBTransaction} tx IndexedDB transaction to back this wrapper.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.db.Transaction = function(tx) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Underlying IndexedDB transaction object.
|
||||
*
|
||||
* @type {!IDBTransaction}
|
||||
* @private
|
||||
*/
|
||||
this.tx_ = tx;
|
||||
|
||||
/**
|
||||
* Event handler for this transaction.
|
||||
*
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
// TODO(user): remove these casts once the externs file is updated to
|
||||
// correctly reflect that IDBTransaction extends EventTarget
|
||||
this.eventHandler_.listen(
|
||||
(/** @type {EventTarget} */ this.tx_),
|
||||
'complete',
|
||||
goog.bind(
|
||||
this.dispatchEvent,
|
||||
this,
|
||||
goog.db.Transaction.EventTypes.COMPLETE));
|
||||
this.eventHandler_.listen(
|
||||
(/** @type {EventTarget} */ this.tx_),
|
||||
'abort',
|
||||
goog.bind(
|
||||
this.dispatchEvent,
|
||||
this,
|
||||
goog.db.Transaction.EventTypes.ABORT));
|
||||
this.eventHandler_.listen(
|
||||
(/** @type {EventTarget} */ this.tx_),
|
||||
'error',
|
||||
this.dispatchError_);
|
||||
};
|
||||
goog.inherits(goog.db.Transaction, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches an error event based on the given event, wrapping the error
|
||||
* if necessary.
|
||||
*
|
||||
* @param {Event} ev The error event given to the underlying IDBTransaction.
|
||||
* @private
|
||||
*/
|
||||
goog.db.Transaction.prototype.dispatchError_ = function(ev) {
|
||||
if (ev.target instanceof goog.db.Error) {
|
||||
this.dispatchEvent({
|
||||
type: goog.db.Transaction.EventTypes.ERROR,
|
||||
target: ev.target
|
||||
});
|
||||
} else {
|
||||
this.dispatchEvent({
|
||||
type: goog.db.Transaction.EventTypes.ERROR,
|
||||
target: new goog.db.Error(
|
||||
(/** @type {IDBRequest} */ (ev.target)).errorCode,
|
||||
'in transaction')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Event types the Transaction can dispatch. COMPLETE events are dispatched
|
||||
* when the transaction is committed. If a transaction is aborted it dispatches
|
||||
* both an ABORT event and an ERROR event with the ABORT_ERR code. Error events
|
||||
* are dispatched on any error.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.db.Transaction.EventTypes = {
|
||||
COMPLETE: 'complete',
|
||||
ABORT: 'abort',
|
||||
ERROR: 'error'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.db.Transaction.TransactionMode} The transaction's mode.
|
||||
*/
|
||||
goog.db.Transaction.prototype.getMode = function() {
|
||||
return /** @type {goog.db.Transaction.TransactionMode} */ (this.tx_.mode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens an object store to do operations on in this transaction. The requested
|
||||
* object store must be one that is in this transaction's scope.
|
||||
* @see goog.db.IndexedDb#createTransaction
|
||||
*
|
||||
* @param {string} name The name of the requested object store.
|
||||
* @return {!goog.db.ObjectStore} The wrapped object store.
|
||||
* @throws {goog.db.Error} In case of error getting the object store.
|
||||
*/
|
||||
goog.db.Transaction.prototype.objectStore = function(name) {
|
||||
try {
|
||||
return new goog.db.ObjectStore(this.tx_.objectStore(name));
|
||||
} catch (err) {
|
||||
throw new goog.db.Error(err.code, 'getting object store ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Aborts this transaction. No pending operations will be applied to the
|
||||
* database. Dispatches an ABORT event.
|
||||
*/
|
||||
goog.db.Transaction.prototype.abort = function() {
|
||||
this.tx_.abort();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.db.Transaction.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
this.eventHandler_.dispose();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The three possible transaction modes.
|
||||
* @see http://www.w3.org/TR/IndexedDB/#idl-def-IDBTransaction
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.db.Transaction.TransactionMode = {
|
||||
READ_ONLY: 0,
|
||||
READ_WRITE: 1,
|
||||
VERSION_CHANGE: 2
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Simple logger that logs to the window console if available.
|
||||
*
|
||||
* Has an autoInstall option which can be put into initialization code, which
|
||||
* will start logging if "Debug=true" is in document.location.href
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Console');
|
||||
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger.Level');
|
||||
goog.require('goog.debug.TextFormatter');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create and install a log handler that logs to window.console if available
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.Console = function() {
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
|
||||
/**
|
||||
* Formatter for formatted output.
|
||||
* @type {!goog.debug.TextFormatter}
|
||||
* @private
|
||||
*/
|
||||
this.formatter_ = new goog.debug.TextFormatter();
|
||||
this.formatter_.showAbsoluteTime = false;
|
||||
this.formatter_.showExceptionText = false;
|
||||
|
||||
this.isCapturing_ = false;
|
||||
this.logBuffer_ = '';
|
||||
|
||||
/**
|
||||
* Loggers that we shouldn't output.
|
||||
* @type {!Object.<boolean>}
|
||||
* @private
|
||||
*/
|
||||
this.filteredLoggers_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text formatter used by this console
|
||||
* @return {!goog.debug.TextFormatter} The text formatter.
|
||||
*/
|
||||
goog.debug.Console.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.Console.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
this.logBuffer = '';
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord The log entry.
|
||||
*/
|
||||
goog.debug.Console.prototype.addLogRecord = function(logRecord) {
|
||||
|
||||
// Check to see if the log record is filtered or not.
|
||||
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var record = this.formatter_.formatRecord(logRecord);
|
||||
var console = goog.debug.Console.console_;
|
||||
if (console) {
|
||||
switch (logRecord.getLevel()) {
|
||||
case goog.debug.Logger.Level.SHOUT:
|
||||
goog.debug.Console.logToConsole_(console, 'info', record);
|
||||
break;
|
||||
case goog.debug.Logger.Level.SEVERE:
|
||||
goog.debug.Console.logToConsole_(console, 'error', record);
|
||||
break;
|
||||
case goog.debug.Logger.Level.WARNING:
|
||||
goog.debug.Console.logToConsole_(console, 'warn', record);
|
||||
break;
|
||||
default:
|
||||
goog.debug.Console.logToConsole_(console, 'debug', record);
|
||||
break;
|
||||
}
|
||||
} else if (window.opera) {
|
||||
// window.opera.postError is considered an undefined property reference
|
||||
// by JSCompiler, so it has to be referenced using array notation instead.
|
||||
window.opera['postError'](record);
|
||||
} else {
|
||||
this.logBuffer_ += record;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to add.
|
||||
*/
|
||||
goog.debug.Console.prototype.addFilter = function(loggerName) {
|
||||
this.filteredLoggers_[loggerName] = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to remove.
|
||||
*/
|
||||
goog.debug.Console.prototype.removeFilter = function(loggerName) {
|
||||
delete this.filteredLoggers_[loggerName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global console logger instance
|
||||
* @type {goog.debug.Console}
|
||||
*/
|
||||
goog.debug.Console.instance = null;
|
||||
|
||||
|
||||
/**
|
||||
* The console to which to log. This is a property so it can be mocked out in
|
||||
* unit testing.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Console.console_ = window.console;
|
||||
|
||||
|
||||
/**
|
||||
* Install the console and start capturing if "Debug=true" is in the page URL
|
||||
*/
|
||||
goog.debug.Console.autoInstall = function() {
|
||||
if (!goog.debug.Console.instance) {
|
||||
goog.debug.Console.instance = new goog.debug.Console();
|
||||
}
|
||||
|
||||
if (window.location.href.indexOf('Debug=true') != -1) {
|
||||
goog.debug.Console.instance.setCapturing(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show an alert with all of the captured debug information.
|
||||
* Information is only captured if console is not available
|
||||
*/
|
||||
goog.debug.Console.show = function() {
|
||||
alert(goog.debug.Console.instance.logBuffer_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs the record to the console using the given function. If the function is
|
||||
* not available on the console object, the log function is used instead.
|
||||
* @param {!Object} console The console object.
|
||||
* @param {string} fnName The name of the function to use.
|
||||
* @param {string} record The record to log.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Console.logToConsole_ = function(console, fnName, record) {
|
||||
if (console[fnName]) {
|
||||
console[fnName](record);
|
||||
} else {
|
||||
console.log(record);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,492 @@
|
||||
// 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.string');
|
||||
goog.require('goog.structs.Set');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* 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.isVersion('535.3')) {
|
||||
retVal = !retVal;
|
||||
}
|
||||
target.onerror = function(message, url, line) {
|
||||
if (oldErrorHandler) {
|
||||
oldErrorHandler(message, url, line);
|
||||
}
|
||||
logFunc({
|
||||
message: message,
|
||||
fileName: url,
|
||||
line: line
|
||||
});
|
||||
return retVal;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string representing an object and all its properties.
|
||||
* @param {Object|null|undefined} obj Object to expose.
|
||||
* @param {boolean=} opt_showFn Show the functions as well as the properties,
|
||||
* default is false.
|
||||
* @return {string} The string representation of {@code obj}.
|
||||
*/
|
||||
goog.debug.expose = function(obj, opt_showFn) {
|
||||
if (typeof obj == 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
if (obj == null) {
|
||||
return 'NULL';
|
||||
}
|
||||
var str = [];
|
||||
|
||||
for (var x in obj) {
|
||||
if (!opt_showFn && goog.isFunction(obj[x])) {
|
||||
continue;
|
||||
}
|
||||
var s = x + ' = ';
|
||||
/** @preserveTry */
|
||||
try {
|
||||
s += obj[x];
|
||||
} catch (e) {
|
||||
s += '*** ' + e + ' ***';
|
||||
}
|
||||
str.push(s);
|
||||
}
|
||||
return str.join('\n');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string representing a given primitive or object, and for an
|
||||
* object, all its properties and nested objects. WARNING: If an object is
|
||||
* given, it and all its nested objects will be modified. To detect reference
|
||||
* cycles, this method identifies objects using goog.getUid() which mutates the
|
||||
* object.
|
||||
* @param {*} obj Object to expose.
|
||||
* @param {boolean=} opt_showFn Also show properties that are functions (by
|
||||
* default, functions are omitted).
|
||||
* @return {string} A string representation of {@code obj}.
|
||||
*/
|
||||
goog.debug.deepExpose = function(obj, opt_showFn) {
|
||||
var previous = new goog.structs.Set();
|
||||
var str = [];
|
||||
|
||||
var helper = function(obj, space) {
|
||||
var nestspace = space + ' ';
|
||||
|
||||
var indentMultiline = function(str) {
|
||||
return str.replace(/\n/g, '\n' + space);
|
||||
};
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
if (!goog.isDef(obj)) {
|
||||
str.push('undefined');
|
||||
} else if (goog.isNull(obj)) {
|
||||
str.push('NULL');
|
||||
} else if (goog.isString(obj)) {
|
||||
str.push('"' + indentMultiline(obj) + '"');
|
||||
} else if (goog.isFunction(obj)) {
|
||||
str.push(indentMultiline(String(obj)));
|
||||
} else if (goog.isObject(obj)) {
|
||||
if (previous.contains(obj)) {
|
||||
// TODO(user): This is a bug; it falsely detects non-loops as loops
|
||||
// when the reference tree contains two references to the same object.
|
||||
str.push('*** reference loop detected ***');
|
||||
} else {
|
||||
previous.add(obj);
|
||||
str.push('{');
|
||||
for (var x in obj) {
|
||||
if (!opt_showFn && goog.isFunction(obj[x])) {
|
||||
continue;
|
||||
}
|
||||
str.push('\n');
|
||||
str.push(nestspace);
|
||||
str.push(x + ' = ');
|
||||
helper(obj[x], nestspace);
|
||||
}
|
||||
str.push('\n' + space + '}');
|
||||
}
|
||||
} else {
|
||||
str.push(obj);
|
||||
}
|
||||
} catch (e) {
|
||||
str.push('*** ' + e + ' ***');
|
||||
}
|
||||
};
|
||||
|
||||
helper(obj, '');
|
||||
return str.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively outputs a nested array as a string.
|
||||
* @param {Array} arr The array.
|
||||
* @return {string} String representing nested array.
|
||||
*/
|
||||
goog.debug.exposeArray = function(arr) {
|
||||
var str = [];
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (goog.isArray(arr[i])) {
|
||||
str.push(goog.debug.exposeArray(arr[i]));
|
||||
} else {
|
||||
str.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return '[ ' + str.join(', ') + ' ]';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Exposes an exception that has been caught by a try...catch and outputs the
|
||||
* error with a stack trace.
|
||||
* @param {Object} err Error object or string.
|
||||
* @param {Function=} opt_fn Optional function to start stack trace from.
|
||||
* @return {string} Details of exception.
|
||||
*/
|
||||
goog.debug.exposeException = function(err, opt_fn) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var e = goog.debug.normalizeErrorObject(err);
|
||||
|
||||
// Create the error message
|
||||
var error = 'Message: ' + goog.string.htmlEscape(e.message) +
|
||||
'\nUrl: <a href="view-source:' + e.fileName + '" target="_new">' +
|
||||
e.fileName + '</a>\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
|
||||
goog.string.htmlEscape(e.stack + '-> ') +
|
||||
'[end]\n\nJS stack traversal:\n' + goog.string.htmlEscape(
|
||||
goog.debug.getStacktrace(opt_fn) + '-> ');
|
||||
return error;
|
||||
} catch (e2) {
|
||||
return 'Exception trying to expose exception! You win, we lose. ' + e2;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes the error/exception object between browsers.
|
||||
* @param {Object} err Raw error object.
|
||||
* @return {Object} Normalized error object.
|
||||
*/
|
||||
goog.debug.normalizeErrorObject = function(err) {
|
||||
var href = goog.getObjectByName('window.location.href');
|
||||
if (goog.isString(err)) {
|
||||
return {
|
||||
'message': err,
|
||||
'name': 'Unknown error',
|
||||
'lineNumber': 'Not available',
|
||||
'fileName': href,
|
||||
'stack': 'Not available'
|
||||
};
|
||||
}
|
||||
|
||||
var lineNumber, fileName;
|
||||
var threwError = false;
|
||||
|
||||
try {
|
||||
lineNumber = err.lineNumber || err.line || 'Not available';
|
||||
} catch (e) {
|
||||
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
|
||||
// Message: Permission denied to get property UnnamedClass.lineNumber
|
||||
lineNumber = 'Not available';
|
||||
threwError = true;
|
||||
}
|
||||
|
||||
try {
|
||||
fileName = err.fileName || err.filename || err.sourceURL || 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) {
|
||||
return {
|
||||
'message': err.message,
|
||||
'name': err.name,
|
||||
'lineNumber': lineNumber,
|
||||
'fileName': fileName,
|
||||
'stack': err.stack || 'Not available'
|
||||
};
|
||||
}
|
||||
|
||||
// Standards error object
|
||||
return err;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts an object to an Error if it's a String,
|
||||
* adds a stacktrace if there isn't one,
|
||||
* and optionally adds an extra message.
|
||||
* @param {Error|string} err the original thrown object or string.
|
||||
* @param {string=} opt_message optional additional message to add to the
|
||||
* error.
|
||||
* @return {Error} If err is a string, it is used to create a new Error,
|
||||
* which is enhanced and returned. Otherwise err itself is enhanced
|
||||
* and returned.
|
||||
*/
|
||||
goog.debug.enhanceError = function(err, opt_message) {
|
||||
var error = typeof err == 'string' ? Error(err) : err;
|
||||
if (!error.stack) {
|
||||
error.stack = goog.debug.getStacktrace(arguments.callee.caller);
|
||||
}
|
||||
if (opt_message) {
|
||||
// find the first unoccupied 'messageX' property
|
||||
var x = 0;
|
||||
while (error['message' + x]) {
|
||||
++x;
|
||||
}
|
||||
error['message' + x] = String(opt_message);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current stack trace. Simple and iterative - doesn't worry about
|
||||
* catching circular references or getting the args.
|
||||
* @param {number=} opt_depth Optional maximum depth to trace back to.
|
||||
* @return {string} A string with the function names of all functions in the
|
||||
* stack, separated by \n.
|
||||
*/
|
||||
goog.debug.getStacktraceSimple = function(opt_depth) {
|
||||
var sb = [];
|
||||
var fn = arguments.callee.caller;
|
||||
var depth = 0;
|
||||
|
||||
while (fn && (!opt_depth || depth < opt_depth)) {
|
||||
sb.push(goog.debug.getFunctionName(fn));
|
||||
sb.push('()\n');
|
||||
/** @preserveTry */
|
||||
try {
|
||||
fn = fn.caller;
|
||||
} catch (e) {
|
||||
sb.push('[exception trying to get caller]\n');
|
||||
break;
|
||||
}
|
||||
depth++;
|
||||
if (depth >= goog.debug.MAX_STACK_DEPTH) {
|
||||
sb.push('[...long stack...]');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (opt_depth && depth >= opt_depth) {
|
||||
sb.push('[...reached max depth limit...]');
|
||||
} else {
|
||||
sb.push('[end]');
|
||||
}
|
||||
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Max length of stack to try and output
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.MAX_STACK_DEPTH = 50;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current stack trace, either starting from the caller or starting
|
||||
* from a specified function that's currently on the call stack.
|
||||
* @param {Function=} opt_fn Optional function to start getting the trace from.
|
||||
* If not provided, defaults to the function that called this.
|
||||
* @return {string} Stack trace.
|
||||
*/
|
||||
goog.debug.getStacktrace = function(opt_fn) {
|
||||
return goog.debug.getStacktraceHelper_(opt_fn || arguments.callee.caller, []);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper for getStacktrace().
|
||||
* @param {Function} fn Function to start getting the trace from.
|
||||
* @param {Array} visited List of functions visited so far.
|
||||
* @return {string} Stack trace starting from function fn.
|
||||
* @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;
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.push(', ');
|
||||
}
|
||||
var argDesc;
|
||||
var arg = args[i];
|
||||
switch (typeof arg) {
|
||||
case 'object':
|
||||
argDesc = arg ? 'object' : 'null';
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
argDesc = arg;
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
argDesc = String(arg);
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
argDesc = arg ? 'true' : 'false';
|
||||
break;
|
||||
|
||||
case 'function':
|
||||
argDesc = goog.debug.getFunctionName(arg);
|
||||
argDesc = argDesc ? argDesc : '[fn]';
|
||||
break;
|
||||
|
||||
case 'undefined':
|
||||
default:
|
||||
argDesc = typeof arg;
|
||||
break;
|
||||
}
|
||||
|
||||
if (argDesc.length > 40) {
|
||||
argDesc = argDesc.substr(0, 40) + '...';
|
||||
}
|
||||
sb.push(argDesc);
|
||||
}
|
||||
visited.push(fn);
|
||||
sb.push(')\n');
|
||||
/** @preserveTry */
|
||||
try {
|
||||
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
|
||||
} catch (e) {
|
||||
sb.push('[exception trying to get caller]\n');
|
||||
}
|
||||
|
||||
} else if (fn) {
|
||||
sb.push('[...long stack...]');
|
||||
} else {
|
||||
sb.push('[end]');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set a custom function name resolver.
|
||||
* @param {function(Function): string} resolver Resolves functions to their
|
||||
* names.
|
||||
*/
|
||||
goog.debug.setFunctionResolver = function(resolver) {
|
||||
goog.debug.fnNameResolver_ = resolver;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a function name
|
||||
* @param {Function} fn Function to get name of.
|
||||
* @return {string} Function's name.
|
||||
*/
|
||||
goog.debug.getFunctionName = function(fn) {
|
||||
if (goog.debug.fnNameCache_[fn]) {
|
||||
return goog.debug.fnNameCache_[fn];
|
||||
}
|
||||
if (goog.debug.fnNameResolver_) {
|
||||
var name = goog.debug.fnNameResolver_(fn);
|
||||
if (name) {
|
||||
goog.debug.fnNameCache_[fn] = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristically determine function name based on code.
|
||||
var functionSource = String(fn);
|
||||
if (!goog.debug.fnNameCache_[functionSource]) {
|
||||
var matches = /function ([^\(]+)/.exec(functionSource);
|
||||
if (matches) {
|
||||
var method = matches[1];
|
||||
goog.debug.fnNameCache_[functionSource] = method;
|
||||
} else {
|
||||
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
|
||||
}
|
||||
}
|
||||
|
||||
return goog.debug.fnNameCache_[functionSource];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Makes whitespace visible by replacing it with printable characters.
|
||||
* This is useful in finding diffrences between the expected and the actual
|
||||
* output strings of a testcase.
|
||||
* @param {string} string whose whitespace needs to be made visible.
|
||||
* @return {string} string whose whitespace is made visible.
|
||||
*/
|
||||
goog.debug.makeWhitespaceVisible = function(string) {
|
||||
return string.replace(/ /g, '[_]')
|
||||
.replace(/\f/g, '[f]')
|
||||
.replace(/\n/g, '[n]\n')
|
||||
.replace(/\r/g, '[r]')
|
||||
.replace(/\t/g, '[t]');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hash map for storing function names that have already been looked up.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.fnNameCache_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Resolves functions to their names. Resolved function names will be cached.
|
||||
* @type {function(Function):string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.fnNameResolver_;
|
||||
@@ -0,0 +1,612 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the DebugWindow class. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.DebugWindow');
|
||||
|
||||
goog.require('goog.debug.HtmlFormatter');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.structs.CircularBuffer');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a debug DebugWindow that is bound to the goog.debug.Logger.
|
||||
* It handles log messages and writes them to the DebugWindow. This doesn't
|
||||
* provide a lot of functionality that the old Gmail logging infrastructure
|
||||
* provided like saving debug logs for exporting to the server. Now that we
|
||||
* have an event-based logging infrastructure, we can encapsulate that
|
||||
* functionality in a separate class.
|
||||
*
|
||||
* @constructor
|
||||
* @param {string=} opt_identifier Identifier for this logging class.
|
||||
* @param {string=} opt_prefix Prefix prepended to messages.
|
||||
*/
|
||||
goog.debug.DebugWindow = function(opt_identifier, opt_prefix) {
|
||||
/**
|
||||
* Identifier for this logging class
|
||||
* @type {string}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.identifier_ = opt_identifier || '';
|
||||
|
||||
/**
|
||||
* Optional prefix to be prepended to error strings
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.prefix_ = opt_prefix || '';
|
||||
|
||||
/**
|
||||
* Array used to buffer log output
|
||||
* @type {Array}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.outputBuffer_ = [];
|
||||
|
||||
/**
|
||||
* Buffer for saving the last 1000 messages
|
||||
* @type {goog.structs.CircularBuffer}
|
||||
* @private
|
||||
*/
|
||||
this.savedMessages_ =
|
||||
new goog.structs.CircularBuffer(goog.debug.DebugWindow.MAX_SAVED);
|
||||
|
||||
/**
|
||||
* Save the publish handler so it can be removed
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
|
||||
/**
|
||||
* Formatter for formatted output
|
||||
* @type {goog.debug.Formatter}
|
||||
* @private
|
||||
*/
|
||||
this.formatter_ = new goog.debug.HtmlFormatter(this.prefix_);
|
||||
|
||||
/**
|
||||
* Loggers that we shouldn't output
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.filteredLoggers_ = {};
|
||||
|
||||
// enable by default
|
||||
this.setCapturing(true);
|
||||
|
||||
/**
|
||||
* Whether we are currently enabled. When the DebugWindow is enabled, it tries
|
||||
* to keep its window open. When it's disabled, it can still be capturing log
|
||||
* output if, but it won't try to write them to the DebugWindow window until
|
||||
* it's enabled.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.enabled_ = goog.debug.DebugWindow.isEnabled(this.identifier_);
|
||||
|
||||
// timer to save the DebugWindow's window position in a cookie
|
||||
goog.global.setInterval(goog.bind(this.saveWindowPositionSize_, this), 7500);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Max number of messages to be saved
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.DebugWindow.MAX_SAVED = 500;
|
||||
|
||||
|
||||
/**
|
||||
* How long to keep the cookies for in milliseconds
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.DebugWindow.COOKIE_TIME = 30 * 24 * 60 * 60 * 1000; // 30-days
|
||||
|
||||
|
||||
/**
|
||||
* HTML string printed when the debug window opens
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.welcomeMessage = 'LOGGING';
|
||||
|
||||
|
||||
/**
|
||||
* Whether to force enable the window on a severe log.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.enableOnSevere_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to debug window
|
||||
* @type {Window}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.win_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* In the process of opening the window
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.winOpening_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we are currently capturing logger output.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isCapturing_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether we already showed an alert that the DebugWindow was blocked.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.showedBlockedAlert_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to timeout used to buffer the output stream.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.bufferTimeout_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Timestamp for the last time the log was written to.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.lastCall_ = goog.now();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the welcome message shown when the window is first opened or reset.
|
||||
*
|
||||
* @param {string} msg An HTML string.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setWelcomeMessage = function(msg) {
|
||||
this.welcomeMessage = msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the debug window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.init = function() {
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the DebugWindow is enabled. When the DebugWindow is enabled, it
|
||||
* tries to keep its window open and logs all messages to the window. When the
|
||||
* DebugWindow is disabled, it stops logging messages to its window.
|
||||
*
|
||||
* @return {boolean} Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isEnabled = function() {
|
||||
return this.enabled_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the DebugWindow is enabled. When the DebugWindow is enabled, it
|
||||
* tries to keep its window open and log all messages to the window. When the
|
||||
* DebugWindow is disabled, it stops logging messages to its window. The
|
||||
* DebugWindow also saves this state to a cookie so that it's persisted across
|
||||
* application refreshes.
|
||||
* @param {boolean} enable Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setEnabled = function(enable) {
|
||||
this.enabled_ = enable;
|
||||
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
}
|
||||
|
||||
this.setCookie_('enabled', enable ? '1' : '0');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the debug window should be force enabled when a severe log is
|
||||
* encountered.
|
||||
* @param {boolean} enableOnSevere Whether to enable on severe logs..
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setForceEnableOnSevere =
|
||||
function(enableOnSevere) {
|
||||
this.enableOnSevere_ = enableOnSevere;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether we are currently capturing logger output.
|
||||
* @return {boolean} whether we are currently capturing logger output.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.isCapturing = function() {
|
||||
return this.isCapturing_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the formatter for outputting to the debug window. The default formatter
|
||||
* is an instance of goog.debug.HtmlFormatter
|
||||
* @return {goog.debug.Formatter} The formatter in use.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the formatter for outputting to the debug window.
|
||||
* @param {goog.debug.Formatter} formatter The formatter to use.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setFormatter = function(formatter) {
|
||||
this.formatter_ = formatter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a separator to the debug window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addSeparator = function() {
|
||||
this.write_('<hr>');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether there is an active window.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.hasActiveWindow = function() {
|
||||
return !!this.win_ && !this.win_.closed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the contents of the debug window
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.clear_ = function() {
|
||||
this.savedMessages_.clear();
|
||||
if (this.hasActiveWindow()) {
|
||||
this.writeInitialDocument();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord the LogRecord.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addLogRecord = function(logRecord) {
|
||||
if (this.filteredLoggers_[logRecord.getLoggerName()]) {
|
||||
return;
|
||||
}
|
||||
var html = this.formatter_.formatRecord(logRecord);
|
||||
this.write_(html);
|
||||
if (this.enableOnSevere_ &&
|
||||
logRecord.getLevel().value >= goog.debug.Logger.Level.SEVERE.value) {
|
||||
this.setEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes a message to the log, possibly opening up the window if it's enabled,
|
||||
* or saving it if it's disabled.
|
||||
* @param {string} html The HTML to write.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.write_ = function(html) {
|
||||
// If the logger is enabled, open window and write html message to log
|
||||
// otherwise save it
|
||||
if (this.enabled_) {
|
||||
this.openWindow_();
|
||||
this.savedMessages_.add(html);
|
||||
this.writeToLog_(html);
|
||||
} else {
|
||||
this.savedMessages_.add(html);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write to the buffer. If a message hasn't been sent for more than 750ms just
|
||||
* write, otherwise delay for a minimum of 250ms.
|
||||
* @param {string} html HTML to post to the log.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeToLog_ = function(html) {
|
||||
this.outputBuffer_.push(html);
|
||||
goog.global.clearTimeout(this.bufferTimeout_);
|
||||
|
||||
if (goog.now() - this.lastCall_ > 750) {
|
||||
this.writeBufferToLog();
|
||||
} else {
|
||||
this.bufferTimeout_ =
|
||||
goog.global.setTimeout(goog.bind(this.writeBufferToLog, this), 250);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write to the log and maybe scroll into view.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeBufferToLog = function() {
|
||||
this.lastCall_ = goog.now();
|
||||
if (this.hasActiveWindow()) {
|
||||
var body = this.win_.document.body;
|
||||
var scroll = body &&
|
||||
body.scrollHeight - (body.scrollTop + body.clientHeight) <= 100;
|
||||
|
||||
this.win_.document.write(this.outputBuffer_.join(''));
|
||||
this.outputBuffer_.length = 0;
|
||||
|
||||
if (scroll) {
|
||||
this.win_.scrollTo(0, 1000000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes all saved messages to the DebugWindow.
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeSavedMessages_ = function() {
|
||||
var messages = this.savedMessages_.getValues();
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
this.writeToLog_(messages[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens the debug window if it is not already referenced
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.openWindow_ = function() {
|
||||
if (this.hasActiveWindow() || this.winOpening_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var winpos = this.getCookie_('dbg', '0,0,800,500').split(',');
|
||||
var x = Number(winpos[0]);
|
||||
var y = Number(winpos[1]);
|
||||
var w = Number(winpos[2]);
|
||||
var h = Number(winpos[3]);
|
||||
|
||||
this.winOpening_ = true;
|
||||
this.win_ = window.open('', this.getWindowName_(), 'width=' + w +
|
||||
',height=' + h + ',toolbar=no,resizable=yes,' +
|
||||
'scrollbars=yes,left=' + x + ',top=' + y +
|
||||
',status=no,screenx=' + x + ',screeny=' + y);
|
||||
|
||||
if (!this.win_) {
|
||||
if (!this.showedBlockedAlert_) {
|
||||
// only show this once
|
||||
alert('Logger popup was blocked');
|
||||
this.showedBlockedAlert_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.winOpening_ = false;
|
||||
|
||||
if (this.win_) {
|
||||
this.writeInitialDocument();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a valid window name for the debug window. Replaces invalid characters in
|
||||
* IE.
|
||||
* @return {string} Valid window name.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getWindowName_ = function() {
|
||||
return goog.userAgent.IE ?
|
||||
this.identifier_.replace(/[\s\-\.\,]/g, '_') : this.identifier_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The style rule text, for inclusion in the initial HTML.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getStyleRules = function() {
|
||||
return '*{font:normal 14px monospace;}' +
|
||||
'.dbg-sev{color:#F00}' +
|
||||
'.dbg-w{color:#E92}' +
|
||||
'.dbg-sh{background-color:#fd4;font-weight:bold;color:#000}' +
|
||||
'.dbg-i{color:#666}' +
|
||||
'.dbg-f{color:#999}' +
|
||||
'.dbg-ev{color:#0A0}' +
|
||||
'.dbg-m{color:#990}';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Writes the initial HTML of the debug window.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.writeInitialDocument = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.win_.document.open();
|
||||
|
||||
var html = '<style>' + this.getStyleRules() + '</style>' +
|
||||
'<hr><div class="dbg-ev" style="text-align:center">' +
|
||||
this.welcomeMessage + '<br><small>Logger: ' +
|
||||
this.identifier_ + '</small></div><hr>';
|
||||
|
||||
this.writeToLog_(html);
|
||||
this.writeSavedMessages_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Save persistent data (using cookies) for 1 month (cookie specific to this
|
||||
* logger object)
|
||||
* @param {string} key Data name.
|
||||
* @param {string} value Data value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.setCookie_ = function(key, value) {
|
||||
key += this.identifier_;
|
||||
document.cookie = key + '=' + encodeURIComponent(value) +
|
||||
';path=/;expires=' +
|
||||
(new Date(goog.now() + goog.debug.DebugWindow.COOKIE_TIME)).toUTCString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve data (using cookies).
|
||||
* @param {string} key Data name.
|
||||
* @param {string=} opt_default Optional default value if cookie doesn't exist.
|
||||
* @return {string} Cookie value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.getCookie_ = function(key, opt_default) {
|
||||
return goog.debug.DebugWindow.getCookieValue_(
|
||||
this.identifier_, key, opt_default);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve data (using cookies).
|
||||
* @param {string} identifier Identifier for logging class.
|
||||
* @param {string} key Data name.
|
||||
* @param {string=} opt_default Optional default value if cookie doesn't exist.
|
||||
* @return {string} Cookie value.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.getCookieValue_ = function(
|
||||
identifier, key, opt_default) {
|
||||
var fullKey = key + identifier;
|
||||
var cookie = String(document.cookie);
|
||||
var start = cookie.indexOf(fullKey + '=');
|
||||
if (start != -1) {
|
||||
var end = cookie.indexOf(';', start);
|
||||
return decodeURIComponent(cookie.substring(start + fullKey.length + 1,
|
||||
end == -1 ? cookie.length : end));
|
||||
} else {
|
||||
return opt_default || '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} identifier Identifier for logging class.
|
||||
* @return {boolean} Whether the DebugWindow is enabled.
|
||||
*/
|
||||
goog.debug.DebugWindow.isEnabled = function(identifier) {
|
||||
return goog.debug.DebugWindow.getCookieValue_(identifier, 'enabled') == '1';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Saves the window position size to a cookie
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.saveWindowPositionSize_ = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
var x = this.win_.screenX || this.win_.screenLeft || 0;
|
||||
var y = this.win_.screenY || this.win_.screenTop || 0;
|
||||
var w = this.win_.outerWidth || 800;
|
||||
var h = this.win_.outerHeight || 500;
|
||||
this.setCookie_('dbg', x + ',' + y + ',' + w + ',' + h);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to add.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.addFilter = function(loggerName) {
|
||||
this.filteredLoggers_[loggerName] = 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a logger name to be filtered.
|
||||
* @param {string} loggerName the logger name to remove.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.removeFilter = function(loggerName) {
|
||||
delete this.filteredLoggers_[loggerName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Modify the size of the circular buffer. Allows the log to retain more
|
||||
* information while the window is closed.
|
||||
* @param {number} size New size of the circular buffer.
|
||||
*/
|
||||
goog.debug.DebugWindow.prototype.resetBufferWithNewSize = function(size) {
|
||||
if (size > 0 && size < 50000) {
|
||||
this.clear_();
|
||||
this.savedMessages_ = new goog.structs.CircularBuffer(size);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,436 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
goog.provide('goog.debug.DevCss');
|
||||
goog.provide('goog.debug.DevCss.UserAgent');
|
||||
|
||||
goog.require('goog.cssom');
|
||||
goog.require('goog.dom.classes');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for solving development CSS issues/emulating the CSS Compiler.
|
||||
* @param {goog.debug.DevCss.UserAgent=} opt_userAgent The user agent, if not
|
||||
* passed in, will be determined using goog.userAgent.
|
||||
* @param {number|string=} opt_userAgentVersion The user agent's version.
|
||||
* If not passed in, will be determined using goog.userAgent.
|
||||
* @throws {Error} When userAgent detection fails.
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.DevCss = function(opt_userAgent, opt_userAgentVersion) {
|
||||
if (!opt_userAgent) {
|
||||
// Walks through the known goog.userAgents.
|
||||
if (goog.userAgent.IE) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.IE;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.GECKO;
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.WEBKIT;
|
||||
} else if (goog.userAgent.MOBILE) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.MOBILE;
|
||||
} else if (goog.userAgent.OPERA) {
|
||||
opt_userAgent = goog.debug.DevCss.UserAgent.OPERA;
|
||||
}
|
||||
}
|
||||
switch (opt_userAgent) {
|
||||
case goog.debug.DevCss.UserAgent.OPERA:
|
||||
case goog.debug.DevCss.UserAgent.IE:
|
||||
case goog.debug.DevCss.UserAgent.GECKO:
|
||||
case goog.debug.DevCss.UserAgent.FIREFOX:
|
||||
case goog.debug.DevCss.UserAgent.WEBKIT:
|
||||
case goog.debug.DevCss.UserAgent.SAFARI:
|
||||
case goog.debug.DevCss.UserAgent.MOBILE:
|
||||
break;
|
||||
default:
|
||||
throw Error('Could not determine the user agent from known UserAgents');
|
||||
}
|
||||
|
||||
/**
|
||||
* One of goog.debug.DevCss.UserAgent.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.userAgent_ = opt_userAgent;
|
||||
|
||||
/**
|
||||
* @type {number|string}
|
||||
* @private
|
||||
*/
|
||||
this.userAgentVersion_ = opt_userAgentVersion || goog.userAgent.VERSION;
|
||||
this.generateUserAgentTokens_();
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isIe6OrLess_ = this.userAgent_ == goog.debug.DevCss.UserAgent.IE &&
|
||||
goog.string.compareVersions('7', this.userAgentVersion_) > 0;
|
||||
|
||||
if (this.isIe6OrLess_) {
|
||||
/**
|
||||
* @type {Array.<{classNames,combinedClassName,els}>}
|
||||
* @private
|
||||
*/
|
||||
this.ie6CombinedMatches_ = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rewrites the CSSOM as needed to activate any useragent-specific selectors.
|
||||
* @param {boolean=} opt_enableIe6ReadyHandler If true(the default), and the
|
||||
* userAgent is ie6, we set a document "ready" event handler to walk the DOM
|
||||
* and make combined selector className changes. Having this parameter also
|
||||
* aids unit testing.
|
||||
*/
|
||||
goog.debug.DevCss.prototype.activateBrowserSpecificCssRules = function(
|
||||
opt_enableIe6ReadyHandler) {
|
||||
var enableIe6EventHandler = goog.isDef(opt_enableIe6ReadyHandler) ?
|
||||
opt_enableIe6ReadyHandler : true;
|
||||
var cssRules = goog.cssom.getAllCssStyleRules();
|
||||
|
||||
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
|
||||
this.replaceBrowserSpecificClassNames_(cssRule);
|
||||
}
|
||||
|
||||
// Since we may have manipulated the rules above, we'll have to do a
|
||||
// complete sweep again if we're in IE6. Luckily performance doesn't
|
||||
// matter for this tool.
|
||||
if (this.isIe6OrLess_) {
|
||||
cssRules = goog.cssom.getAllCssStyleRules();
|
||||
for (var i = 0, cssRule; cssRule = cssRules[i]; i++) {
|
||||
this.replaceIe6CombinedSelectors_(cssRule);
|
||||
}
|
||||
}
|
||||
|
||||
// Add an event listener for document ready to rewrite any necessary
|
||||
// combined classnames in IE6.
|
||||
if (this.isIe6OrLess_ && enableIe6EventHandler) {
|
||||
goog.events.listen(document, goog.events.EventType.LOAD, goog.bind(
|
||||
this.addIe6CombinedClassNames_, this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.userAgentTokens_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* A list of possible user agent strings.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.debug.DevCss.UserAgent = {
|
||||
OPERA: 'OPERA',
|
||||
IE: 'IE',
|
||||
GECKO: 'GECKO',
|
||||
FIREFOX: 'GECKO',
|
||||
WEBKIT: 'WEBKIT',
|
||||
SAFARI: 'WEBKIT',
|
||||
MOBILE: 'MOBILE'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A list of strings that may be used for matching in CSS files/development.
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.CssToken_ = {
|
||||
USERAGENT: 'USERAGENT',
|
||||
SEPARATOR: '-',
|
||||
LESS_THAN: 'LT',
|
||||
GREATER_THAN: 'GT',
|
||||
LESS_THAN_OR_EQUAL: 'LTE',
|
||||
GREATER_THAN_OR_EQUAL: 'GTE',
|
||||
IE6_SELECTOR_TEXT: 'goog-ie6-selector',
|
||||
IE6_COMBINED_GLUE: '_'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generates user agent token match strings with comparison and version bits.
|
||||
* For example:
|
||||
* userAgentTokens_.ANY will be like 'GECKO'
|
||||
* userAgentTokens_.LESS_THAN will be like 'GECKO-LT3' etc...
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.generateUserAgentTokens_ = function() {
|
||||
this.userAgentTokens_.ANY = goog.debug.DevCss.CssToken_.USERAGENT +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR + this.userAgent_;
|
||||
this.userAgentTokens_.EQUALS = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR;
|
||||
this.userAgentTokens_.LESS_THAN = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.LESS_THAN;
|
||||
this.userAgentTokens_.LESS_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.LESS_THAN_OR_EQUAL;
|
||||
this.userAgentTokens_.GREATER_THAN = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.GREATER_THAN;
|
||||
this.userAgentTokens_.GREATER_THAN_OR_EQUAL = this.userAgentTokens_.ANY +
|
||||
goog.debug.DevCss.CssToken_.SEPARATOR +
|
||||
goog.debug.DevCss.CssToken_.GREATER_THAN_OR_EQUAL;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the version number bit from a selector matching userAgentToken.
|
||||
* @param {string} selectorText The selector text of a CSS rule.
|
||||
* @param {string} userAgentToken Includes the LTE/GTE bit to see if it matches.
|
||||
* @return {string|undefined} The version number.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getVersionNumberFromSelectorText_ = function(
|
||||
selectorText, userAgentToken) {
|
||||
var regex = new RegExp(userAgentToken + '([\\d\\.]+)');
|
||||
var matches = regex.exec(selectorText);
|
||||
if (matches && matches.length == 2) {
|
||||
return matches[1];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Extracts a rule version from the selector text, and if it finds one, calls
|
||||
* compareVersions against it and the passed in token string to provide the
|
||||
* value needed to determine if we have a match or not.
|
||||
* @param {CSSRule} cssRule The rule to test against.
|
||||
* @param {string} token The match token to test against the rule.
|
||||
* @return {Array|undefined} A tuple with the result of the compareVersions call
|
||||
* and the matched ruleVersion.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getRuleVersionAndCompare_ = function(cssRule,
|
||||
token) {
|
||||
if (!cssRule.selectorText.match(token)) {
|
||||
return;
|
||||
}
|
||||
var ruleVersion = this.getVersionNumberFromSelectorText_(
|
||||
cssRule.selectorText, token);
|
||||
if (!ruleVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
var comparison = goog.string.compareVersions(this.userAgentVersion_,
|
||||
ruleVersion);
|
||||
return [comparison, ruleVersion];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces a CSS selector if we have matches based on our useragent/version.
|
||||
* Example: With a selector like ".USERAGENT-IE-LTE6 .class { prop: value }" if
|
||||
* we are running IE6 we'll end up with ".class { prop: value }", thereby
|
||||
* "activating" the selector.
|
||||
* @param {CSSRule} cssRule The cssRule to potentially replace.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.replaceBrowserSpecificClassNames_ = function(
|
||||
cssRule) {
|
||||
|
||||
// If we don't match the browser token, we can stop now.
|
||||
if (!cssRule.selectorText.match(this.userAgentTokens_.ANY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We know it will begin as a classname.
|
||||
var additionalRegexString;
|
||||
|
||||
// Tests "Less than or equals".
|
||||
var compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.LESS_THAN_OR_EQUAL);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] > 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.LESS_THAN_OR_EQUAL +
|
||||
compared[1];
|
||||
}
|
||||
|
||||
// Tests "Less than".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.LESS_THAN);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] > -1) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.LESS_THAN + compared[1];
|
||||
}
|
||||
|
||||
// Tests "Greater than or equals".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.GREATER_THAN_OR_EQUAL);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] < 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.GREATER_THAN_OR_EQUAL +
|
||||
compared[1];
|
||||
}
|
||||
|
||||
// Tests "Greater than".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.GREATER_THAN);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] < 1) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.GREATER_THAN + compared[1];
|
||||
}
|
||||
|
||||
// Tests "Equals".
|
||||
compared = this.getRuleVersionAndCompare_(cssRule,
|
||||
this.userAgentTokens_.EQUALS);
|
||||
if (compared && compared.length) {
|
||||
if (compared[0] != 0) {
|
||||
return;
|
||||
}
|
||||
additionalRegexString = this.userAgentTokens_.EQUALS + compared[1];
|
||||
}
|
||||
|
||||
// If we got to here without generating the additionalRegexString, then
|
||||
// we did not match any of our comparison token strings, and we want a
|
||||
// general browser token replacement.
|
||||
if (!additionalRegexString) {
|
||||
additionalRegexString = this.userAgentTokens_.ANY;
|
||||
}
|
||||
|
||||
// We need to match at least a single whitespace character to know that
|
||||
// we are matching the entire useragent string token.
|
||||
var regexString = '\\.' + additionalRegexString + '\\s+';
|
||||
var re = new RegExp(regexString, 'g');
|
||||
|
||||
var currentCssText = goog.cssom.getCssTextFromCssRule(cssRule);
|
||||
|
||||
// Replacing the token with '' activates the selector for this useragent.
|
||||
var newCssText = currentCssText.replace(re, '');
|
||||
|
||||
if (newCssText != currentCssText) {
|
||||
goog.cssom.replaceCssRule(cssRule, newCssText);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces IE6 combined selector rules with a workable development alternative.
|
||||
* IE6 actually parses .class1.class2 {} to simply .class2 {} which is nasty.
|
||||
* To fully support combined selectors in IE6 this function needs to be paired
|
||||
* with a call to replace the relevant DOM elements classNames as well.
|
||||
* @see {this.addIe6CombinedClassNames_}
|
||||
* @param {CSSRule} cssRule The rule to potentially fix.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.replaceIe6CombinedSelectors_ = function(cssRule) {
|
||||
// This match only ever works in IE because other UA's won't have our
|
||||
// IE6_SELECTOR_TEXT in the cssText property.
|
||||
if (cssRule.style.cssText &&
|
||||
cssRule.style.cssText.match(
|
||||
goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT)) {
|
||||
var cssText = goog.cssom.getCssTextFromCssRule(cssRule);
|
||||
var combinedSelectorText = this.getIe6CombinedSelectorText_(cssText);
|
||||
if (combinedSelectorText) {
|
||||
var newCssText = combinedSelectorText + '{' + cssRule.style.cssText + '}';
|
||||
goog.cssom.replaceCssRule(cssRule, newCssText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the appropriate new combined selector text for IE6.
|
||||
* Also adds an entry onto ie6CombinedMatches_ with relevant info for the
|
||||
* likely following call to walk the DOM and rewrite the class attribute.
|
||||
* Example: With a selector like
|
||||
* ".class2 { -goog-ie6-selector: .class1.class2; prop: value }".
|
||||
* this function will return:
|
||||
* ".class1_class2 { prop: value }".
|
||||
* @param {string} cssText The CSS selector text and css rule text combined.
|
||||
* @return {?string} The rewritten css rule text.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.getIe6CombinedSelectorText_ = function(cssText) {
|
||||
var regex = new RegExp(goog.debug.DevCss.CssToken_.IE6_SELECTOR_TEXT +
|
||||
'\\s*:\\s*\\"([^\\"]+)\\"', 'gi');
|
||||
var matches = regex.exec(cssText);
|
||||
if (matches) {
|
||||
var combinedSelectorText = matches[1];
|
||||
// To aid in later fixing the DOM, we need to split up the possible
|
||||
// selector groups by commas.
|
||||
var groupedSelectors = combinedSelectorText.split(/\s*\,\s*/);
|
||||
for (var i = 0, selector; selector = groupedSelectors[i]; i++) {
|
||||
// Strips off the leading ".".
|
||||
var combinedClassName = selector.substr(1);
|
||||
var classNames = combinedClassName.split(
|
||||
goog.debug.DevCss.CssToken_.IE6_COMBINED_GLUE);
|
||||
var entry = {
|
||||
classNames: classNames,
|
||||
combinedClassName: combinedClassName,
|
||||
els: []
|
||||
};
|
||||
this.ie6CombinedMatches_.push(entry);
|
||||
}
|
||||
return combinedSelectorText;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds combined selectors with underscores to make them "work" in IE6.
|
||||
* @see {this.replaceIe6CombinedSelectors_}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.DevCss.prototype.addIe6CombinedClassNames_ = function() {
|
||||
if (!this.ie6CombinedMatches_.length) {
|
||||
return;
|
||||
}
|
||||
var allEls = document.getElementsByTagName('*');
|
||||
var matches = [];
|
||||
// Match nodes for all classNames.
|
||||
for (var i = 0, classNameEntry; classNameEntry =
|
||||
this.ie6CombinedMatches_[i]; i++) {
|
||||
for (var j = 0, el; el = allEls[j]; j++) {
|
||||
var classNamesLength = classNameEntry.classNames.length;
|
||||
for (var k = 0, className; className = classNameEntry.classNames[k];
|
||||
k++) {
|
||||
if (!goog.dom.classes.has(el, className)) {
|
||||
break;
|
||||
}
|
||||
if (k == classNamesLength - 1) {
|
||||
classNameEntry.els.push(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Walks over our matching nodes and fixes them.
|
||||
if (classNameEntry.els.length) {
|
||||
for (var j = 0, el; el = classNameEntry.els[j]; j++) {
|
||||
if (!goog.dom.classes.has(el, classNameEntry.combinedClassName)) {
|
||||
goog.dom.classes.add(el, classNameEntry.combinedClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Development CSS Compiler runtime execution.
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.devCssRunner');
|
||||
|
||||
goog.require('goog.debug.DevCss');
|
||||
|
||||
(function() {
|
||||
var devCssInstance = new goog.debug.DevCss();
|
||||
devCssInstance.activateBrowserSpecificCssRules();
|
||||
})();
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Simple logger that logs a Div Element.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.DivConsole');
|
||||
|
||||
goog.require('goog.debug.HtmlFormatter');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.style');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class for visualising logger calls in a div element.
|
||||
* @param {Element} element The element to append to.
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.DivConsole = function(element) {
|
||||
this.publishHandler_ = goog.bind(this.addLogRecord, this);
|
||||
this.formatter_ = new goog.debug.HtmlFormatter();
|
||||
this.formatter_.showAbsoluteTime = false;
|
||||
this.isCapturing_ = false;
|
||||
this.element_ = element;
|
||||
this.elementOwnerDocument_ =
|
||||
this.element_.ownerDocument || this.element_.document;
|
||||
|
||||
this.installStyles();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs styles for the log messages and its div
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.installStyles = function() {
|
||||
goog.style.installStyles(
|
||||
'.dbg-sev{color:#F00}' +
|
||||
'.dbg-w{color:#C40}' +
|
||||
'.dbg-sh{font-weight:bold;color:#000}' +
|
||||
'.dbg-i{color:#444}' +
|
||||
'.dbg-f{color:#999}' +
|
||||
'.dbg-ev{color:#0A0}' +
|
||||
'.dbg-m{color:#990}' +
|
||||
'.logmsg{border-bottom:1px solid #CCC;padding:2px}' +
|
||||
'.logsep{background-color: #8C8;}' +
|
||||
'.logdiv{border:1px solid #CCC;background-color:#FCFCFC;' +
|
||||
'font:medium monospace}',
|
||||
this.element_);
|
||||
this.element_.className += ' logdiv';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether we are currently capturing logger output.
|
||||
* @param {boolean} capturing Whether to capture logger output.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.setCapturing = function(capturing) {
|
||||
if (capturing == this.isCapturing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// attach or detach handler from the root logger
|
||||
var rootLogger = goog.debug.LogManager.getRoot();
|
||||
if (capturing) {
|
||||
rootLogger.addHandler(this.publishHandler_);
|
||||
} else {
|
||||
rootLogger.removeHandler(this.publishHandler_);
|
||||
this.logBuffer = '';
|
||||
}
|
||||
this.isCapturing_ = capturing;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record.
|
||||
* @param {goog.debug.LogRecord} logRecord The log entry.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.addLogRecord = function(logRecord) {
|
||||
var scroll = this.element_.scrollHeight - this.element_.scrollTop -
|
||||
this.element_.clientHeight <= 100;
|
||||
|
||||
var div = this.elementOwnerDocument_.createElement('div');
|
||||
div.className = 'logmsg';
|
||||
div.innerHTML = this.formatter_.formatRecord(logRecord);
|
||||
this.element_.appendChild(div);
|
||||
|
||||
if (scroll) {
|
||||
this.element_.scrollTop = this.element_.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the formatter for outputting to the console. The default formatter
|
||||
* is an instance of goog.debug.HtmlFormatter
|
||||
* @return {goog.debug.Formatter} The formatter in use.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.getFormatter = function() {
|
||||
return this.formatter_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the formatter for outputting to the console.
|
||||
* @param {goog.debug.Formatter} formatter The formatter to use.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.setFormatter = function(formatter) {
|
||||
this.formatter_ = formatter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a separator to the debug window.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.addSeparator = function() {
|
||||
var div = this.elementOwnerDocument_.createElement('div');
|
||||
div.className = 'logmsg logsep';
|
||||
this.element_.appendChild(div);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the console.
|
||||
*/
|
||||
goog.debug.DivConsole.prototype.clear = function() {
|
||||
this.element_.innerHTML = '';
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview A global registry for entry points into a program,
|
||||
* so that they can be instrumented. Each module should register their
|
||||
* entry points with this registry. Designed to be compiled out
|
||||
* if no instrumentation is requested.
|
||||
*
|
||||
* Entry points may be registered before or after a call to
|
||||
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
|
||||
* later, the existing monitor will instrument the new entry point.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.EntryPointMonitor');
|
||||
goog.provide('goog.debug.entryPointRegistry');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
goog.debug.EntryPointMonitor = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Instruments a function.
|
||||
*
|
||||
* @param {!Function} fn A function to instrument.
|
||||
* @return {!Function} The instrumented function.
|
||||
*/
|
||||
goog.debug.EntryPointMonitor.prototype.wrap;
|
||||
|
||||
|
||||
/**
|
||||
* Try to remove an instrumentation wrapper created by this monitor.
|
||||
* If the function passed to unwrap is not a wrapper created by this
|
||||
* monitor, then we will do nothing.
|
||||
*
|
||||
* Notice that some wrappers may not be unwrappable. For example, if other
|
||||
* monitors have applied their own wrappers, then it will be impossible to
|
||||
* unwrap them because their wrappers will have captured our wrapper.
|
||||
*
|
||||
* So it is important that entry points are unwrapped in the reverse
|
||||
* order that they were wrapped.
|
||||
*
|
||||
* @param {!Function} fn A function to unwrap.
|
||||
* @return {!Function} The unwrapped function, or {@code fn} if it was not
|
||||
* a wrapped function created by this monitor.
|
||||
*/
|
||||
goog.debug.EntryPointMonitor.prototype.unwrap;
|
||||
|
||||
|
||||
/**
|
||||
* An array of entry point callbacks.
|
||||
* @type {!Array.<function(!Function)>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.refList_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Monitors that should wrap all the entry points.
|
||||
* @type {!Array.<!goog.debug.EntryPointMonitor>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitors_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
|
||||
* Checking this allows the compiler to optimize out the registrations.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Register an entry point with this module.
|
||||
*
|
||||
* The entry point will be instrumented when a monitor is passed to
|
||||
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
|
||||
* entry point is instrumented immediately.
|
||||
*
|
||||
* @param {function(!Function)} callback A callback function which is called
|
||||
* with a transforming function to instrument the entry point. The callback
|
||||
* is responsible for wrapping the relevant entry point with the
|
||||
* transforming function.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.register = function(callback) {
|
||||
// Don't use push(), so that this can be compiled out.
|
||||
goog.debug.entryPointRegistry.refList_[
|
||||
goog.debug.entryPointRegistry.refList_.length] = callback;
|
||||
// If no one calls monitorAll, this can be compiled out.
|
||||
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
|
||||
var monitors = goog.debug.entryPointRegistry.monitors_;
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
callback(goog.bind(monitors[i].wrap, monitors[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configures a monitor to wrap all entry points.
|
||||
*
|
||||
* Entry points that have already been registered are immediately wrapped by
|
||||
* the monitor. When an entry point is registered in the future, it will also
|
||||
* be wrapped by the monitor when it is registered.
|
||||
*
|
||||
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
|
||||
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
|
||||
var transformer = goog.bind(monitor.wrap, monitor);
|
||||
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
|
||||
goog.debug.entryPointRegistry.refList_[i](transformer);
|
||||
}
|
||||
goog.debug.entryPointRegistry.monitors_.push(monitor);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Try to unmonitor all the entry points that have already been registered. If
|
||||
* an entry point is registered in the future, it will not be wrapped by the
|
||||
* monitor when it is registered. Note that this may fail if the entry points
|
||||
* have additional wrapping.
|
||||
*
|
||||
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
|
||||
* the entry points.
|
||||
* @throws {Error} If the monitor is not the most recently configured monitor.
|
||||
*/
|
||||
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
|
||||
var monitors = goog.debug.entryPointRegistry.monitors_;
|
||||
goog.asserts.assert(monitor == monitors[monitors.length - 1],
|
||||
'Only the most recent monitor can be unwrapped.');
|
||||
var transformer = goog.bind(monitor.unwrap, monitor);
|
||||
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
|
||||
goog.debug.entryPointRegistry.refList_[i](transformer);
|
||||
}
|
||||
monitors.length--;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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) {
|
||||
|
||||
// Ensure there is a stack trace.
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, goog.debug.Error);
|
||||
} else {
|
||||
this.stack = new Error().stack || '';
|
||||
}
|
||||
|
||||
if (opt_msg) {
|
||||
this.message = String(opt_msg);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.Error, Error);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.Error.prototype.name = 'CustomError';
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Error handling utilities.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.ErrorHandler');
|
||||
goog.provide('goog.debug.ErrorHandler.ProtectedFunctionError');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.EntryPointMonitor');
|
||||
goog.require('goog.debug.Trace');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The ErrorHandler can be used to to wrap functions with a try/catch
|
||||
* statement. If an exception is thrown, the given error handler function will
|
||||
* be called.
|
||||
*
|
||||
* When this object is disposed, it will stop handling exceptions and tracing.
|
||||
* It will also try to restore window.setTimeout and window.setInterval
|
||||
* if it wrapped them. Notice that in the general case, it is not technically
|
||||
* possible to remove the wrapper, because functions have no knowledge of
|
||||
* what they have been assigned to. So the app is responsible for other
|
||||
* forms of unwrapping.
|
||||
*
|
||||
* @param {Function} handler Handler for exceptions.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
* @implements {goog.debug.EntryPointMonitor}
|
||||
*/
|
||||
goog.debug.ErrorHandler = function(handler) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Handler for exceptions, which can do logging, reporting, etc.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.errorHandlerFn_ = handler;
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorHandler, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* Whether to add tracers when instrumenting entry points.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.addTracersToProtectedFunctions_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Enable tracers when instrumenting entry points.
|
||||
* @param {boolean} newVal See above.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.setAddTracersToProtectedFunctions =
|
||||
function(newVal) {
|
||||
this.addTracersToProtectedFunctions_ = newVal;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.wrap = function(fn) {
|
||||
return this.protectEntryPoint(goog.asserts.assertFunction(fn));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.unwrap = function(fn) {
|
||||
goog.asserts.assertFunction(fn);
|
||||
return fn[this.getFunctionIndex_(false)] || fn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Private helper function to return a span that can be clicked on to display
|
||||
* an alert with the current stack trace. Newlines are replaced with a
|
||||
* placeholder so that they will not be html-escaped.
|
||||
* @param {string} stackTrace The stack trace to create a span for.
|
||||
* @return {string} A span which can be clicked on to show the stack trace.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getStackTraceHolder_ = function(stackTrace) {
|
||||
var buffer = [];
|
||||
buffer.push('##PE_STACK_START##');
|
||||
buffer.push(stackTrace.replace(/(\r\n|\r|\n)/g, '##STACK_BR##'));
|
||||
buffer.push('##PE_STACK_END##');
|
||||
return buffer.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the index for a function. Used for internal indexing.
|
||||
* @param {boolean} wrapper True for the wrapper; false for the wrapped.
|
||||
* @return {string} The index where we should store the function in its
|
||||
* wrapper/wrapped function.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getFunctionIndex_ = function(wrapper) {
|
||||
return (wrapper ? '__wrapper_' : '__protected_') + goog.getUid(this) + '__';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for an entry point function. When an exception
|
||||
* is thrown from a protected function, a handler will be invoked to handle it.
|
||||
*
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @return {!Function} A protected wrapper function that calls the entry point
|
||||
* function.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectEntryPoint = function(fn) {
|
||||
var protectedFnName = this.getFunctionIndex_(true);
|
||||
if (!fn[protectedFnName]) {
|
||||
var wrapper = fn[protectedFnName] = this.getProtectedFunction(fn);
|
||||
wrapper[this.getFunctionIndex_(false)] = fn;
|
||||
}
|
||||
return fn[protectedFnName];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helps {@link #protectEntryPoint} by actually creating the protected
|
||||
* wrapper function, after {@link #protectEntryPoint} determines that one does
|
||||
* not already exist for the given function. Can be overriden by subclasses
|
||||
* that may want to implement different error handling, or add additional
|
||||
* entry point hooks.
|
||||
* @param {!Function} fn An entry point function to be protected.
|
||||
* @return {!Function} protected wrapper function.
|
||||
* @protected
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.getProtectedFunction = function(fn) {
|
||||
var that = this;
|
||||
var tracers = this.addTracersToProtectedFunctions_;
|
||||
if (tracers) {
|
||||
var stackTrace = goog.debug.getStacktraceSimple(15);
|
||||
}
|
||||
var googDebugErrorHandlerProtectedFunction = function() {
|
||||
if (that.isDisposed()) {
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
|
||||
if (tracers) {
|
||||
var tracer = goog.debug.Trace.startTracer('protectedEntryPoint: ' +
|
||||
that.getStackTraceHolder_(stackTrace));
|
||||
}
|
||||
try {
|
||||
return fn.apply(this, arguments);
|
||||
} catch (e) {
|
||||
that.errorHandlerFn_(e);
|
||||
// Re-throw it since this may be expected by the caller.
|
||||
throw new goog.debug.ErrorHandler.ProtectedFunctionError(e);
|
||||
} finally {
|
||||
if (tracers) {
|
||||
goog.debug.Trace.stopTracer(tracer);
|
||||
}
|
||||
}
|
||||
};
|
||||
googDebugErrorHandlerProtectedFunction[this.getFunctionIndex_(false)] = fn;
|
||||
return googDebugErrorHandlerProtectedFunction;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for window.setTimeout to handle exceptions.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowSetTimeout =
|
||||
function() {
|
||||
this.protectWindowFunctionsHelper_('setTimeout');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Install exception protection for window.setInterval to handle exceptions.
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowSetInterval =
|
||||
function() {
|
||||
this.protectWindowFunctionsHelper_('setInterval');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for protecting setTimeout/setInterval.
|
||||
* @param {string} fnName The name of the function we're protecting. Must
|
||||
* be setTimeout or setInterval.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorHandler.prototype.protectWindowFunctionsHelper_ =
|
||||
function(fnName) {
|
||||
var win = goog.getObjectByName('window');
|
||||
var originalFn = win[fnName];
|
||||
var that = this;
|
||||
win[fnName] = function(fn, time) {
|
||||
// Don't try to protect strings. In theory, we could try to globalEval
|
||||
// the string, but this seems to lead to permission errors on IE6.
|
||||
if (goog.isString(fn)) {
|
||||
fn = goog.partial(goog.globalEval, fn);
|
||||
}
|
||||
fn = that.protectEntryPoint(fn);
|
||||
|
||||
// IE doesn't support .call for setInterval/setTimeout, but it
|
||||
// also doesn't care what "this" is, so we can just call the
|
||||
// original function directly
|
||||
if (originalFn.call) {
|
||||
return originalFn.call(this, fn, time);
|
||||
} else {
|
||||
return originalFn(fn, time);
|
||||
}
|
||||
};
|
||||
win[fnName][this.getFunctionIndex_(false)] = originalFn;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorHandler.prototype.disposeInternal = function() {
|
||||
// Try to unwrap window.setTimeout and window.setInterval.
|
||||
var win = goog.getObjectByName('window');
|
||||
win.setTimeout = this.unwrap(win.setTimeout);
|
||||
win.setInterval = this.unwrap(win.setInterval);
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown to the caller of a protected entry point if the entry point
|
||||
* throws an error.
|
||||
* @param {*} cause The error thrown by the entry point.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Error}
|
||||
*/
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError = function(cause) {
|
||||
var message = goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX +
|
||||
(cause && cause.message ? String(cause.message) : String(cause));
|
||||
goog.base(this, message);
|
||||
|
||||
/**
|
||||
* The error thrown by the entry point.
|
||||
* @type {*}
|
||||
*/
|
||||
this.cause = cause;
|
||||
|
||||
var stack = cause && cause.stack;
|
||||
if (stack && goog.isString(stack)) {
|
||||
this.stack = /** @type {string} */ (stack);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorHandler.ProtectedFunctionError, goog.debug.Error);
|
||||
|
||||
|
||||
/**
|
||||
* Text to prefix the message with.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.ErrorHandler.ProtectedFunctionError.MESSAGE_PREFIX =
|
||||
'Error in protected function: ';
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview File which defines dummy object to work around undefined
|
||||
* properties compiler warning for weak dependencies on
|
||||
* {@link goog.debug.ErrorHandler#protectEntryPoint}.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.errorHandlerWeakDep');
|
||||
|
||||
|
||||
/**
|
||||
* Dummy object to work around undefined properties compiler warning.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.debug.errorHandlerWeakDep = {
|
||||
/**
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @param {boolean=} opt_tracers Whether to install tracers around the
|
||||
* fn.
|
||||
* @return {Function} A protected wrapper function that calls the
|
||||
* entry point function.
|
||||
*/
|
||||
protectEntryPoint: function(fn, opt_tracers) { return fn; }
|
||||
};
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the ErrorReporter class, which creates an error
|
||||
* handler that reports any errors raised to a URL.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.ErrorReporter');
|
||||
goog.provide('goog.debug.ErrorReporter.ExceptionEvent');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.ErrorHandler');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.entryPointRegistry');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.net.XhrIo');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.uri.utils');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an error reporter. Internal Use Only. To install an error
|
||||
* reporter see the {@see #install} method below.
|
||||
*
|
||||
* @param {string} handlerUrl The URL to which all errors will be reported.
|
||||
* @param {function(!Error, !Object.<string, string>)=}
|
||||
* opt_contextProvider When a report is to be sent to the server,
|
||||
* this method will be called, and given an opportunity to modify the
|
||||
* context object before submission to the server.
|
||||
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
|
||||
* onerror and to protect entry points. If apps have other error reporting
|
||||
* facilities, it may make sense for them to set these up themselves and use
|
||||
* the ErrorReporter just for transmission of reports.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.debug.ErrorReporter = function(
|
||||
handlerUrl, opt_contextProvider, opt_noAutoProtect) {
|
||||
/**
|
||||
* Context provider, if one was provided.
|
||||
* @type {?function(!Error, !Object.<string, string>)}
|
||||
* @private
|
||||
*/
|
||||
this.contextProvider_ = opt_contextProvider || null;
|
||||
|
||||
/**
|
||||
* XHR sender.
|
||||
* @type {function(string, string, string, (Object|goog.structs.Map)=)}
|
||||
* @private
|
||||
*/
|
||||
this.xhrSender_ = goog.debug.ErrorReporter.defaultXhrSender;
|
||||
|
||||
/**
|
||||
* The URL at which all errors caught by this handler will be logged.
|
||||
*
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.handlerUrl_ = handlerUrl;
|
||||
|
||||
if (!opt_noAutoProtect) {
|
||||
this.setup_();
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorReporter, goog.events.EventTarget);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Event broadcast when an exception is logged.
|
||||
* @param {Error} error The exception that was was reported.
|
||||
* @param {!Object.<string, string>} context The context values sent to the
|
||||
* server alongside this error.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.debug.ErrorReporter.ExceptionEvent = function(error, context) {
|
||||
goog.events.Event.call(this, goog.debug.ErrorReporter.ExceptionEvent.TYPE);
|
||||
|
||||
/**
|
||||
* The error that was reported.
|
||||
* @type {Error}
|
||||
*/
|
||||
this.error = error;
|
||||
|
||||
/**
|
||||
* Context values sent to the server alongside this report.
|
||||
* @type {!Object.<string, string>}
|
||||
*/
|
||||
this.context = context;
|
||||
};
|
||||
goog.inherits(goog.debug.ErrorReporter.ExceptionEvent, goog.events.Event);
|
||||
|
||||
|
||||
/**
|
||||
* Event type for notifying of a logged exception.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.ErrorReporter.ExceptionEvent.TYPE =
|
||||
goog.events.getUniqueId('exception');
|
||||
|
||||
|
||||
/**
|
||||
* The internal error handler used to catch all errors.
|
||||
*
|
||||
* @type {goog.debug.ErrorHandler}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.errorHandler_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Extra headers for the error-reporting XHR.
|
||||
* @type {Object|goog.structs.Map|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.extraHeaders_;
|
||||
|
||||
|
||||
/**
|
||||
* Logging object.
|
||||
*
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.logger_ =
|
||||
goog.debug.Logger.getLogger('goog.debug.ErrorReporter');
|
||||
|
||||
|
||||
/**
|
||||
* Installs an error reporter to catch all JavaScript errors raised.
|
||||
*
|
||||
* @param {string} loggingUrl The URL to which the errors caught will be
|
||||
* reported.
|
||||
* @param {function(!Error, !Object.<string, string>)=}
|
||||
* opt_contextProvider When a report is to be sent to the server,
|
||||
* this method will be called, and given an opportunity to modify the
|
||||
* context object before submission to the server.
|
||||
* @param {boolean=} opt_noAutoProtect Whether to automatically add handlers for
|
||||
* onerror and to protect entry points. If apps have other error reporting
|
||||
* facilities, it may make sense for them to set these up themselves and use
|
||||
* the ErrorReporter just for transmission of reports.
|
||||
* @return {goog.debug.ErrorReporter} The error reporter.
|
||||
*/
|
||||
goog.debug.ErrorReporter.install = function(
|
||||
loggingUrl, opt_contextProvider, opt_noAutoProtect) {
|
||||
var instance = new goog.debug.ErrorReporter(
|
||||
loggingUrl, opt_contextProvider, opt_noAutoProtect);
|
||||
return instance;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default implemntation of XHR sender interface.
|
||||
*
|
||||
* @param {string} uri URI to make request to.
|
||||
* @param {string} method Send method.
|
||||
* @param {string} content Post data.
|
||||
* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the
|
||||
* request.
|
||||
*/
|
||||
goog.debug.ErrorReporter.defaultXhrSender = function(uri, method, content,
|
||||
opt_headers) {
|
||||
goog.net.XhrIo.send(uri, null, method, content, opt_headers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Installs exception protection for an entry point function in addition
|
||||
* to those that are protected by default.
|
||||
* Has no effect in IE because window.onerror is used for reporting
|
||||
* exceptions in that case.
|
||||
*
|
||||
* @param {Function} fn An entry point function to be protected.
|
||||
* @return {Function} A protected wrapper function that calls the entry point
|
||||
* function or null if the entry point could not be protected.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.protectAdditionalEntryPoint = function(fn) {
|
||||
if (this.errorHandler_) {
|
||||
return this.errorHandler_.protectEntryPoint(fn);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add headers to the logging url.
|
||||
* @param {Object|goog.structs.Map} loggingHeaders Extra headers to send
|
||||
* to the logging URL.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setLoggingHeaders =
|
||||
function(loggingHeaders) {
|
||||
this.extraHeaders_ = loggingHeaders;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the function used to send error reports to the server.
|
||||
* @param {function(string, string, string, (Object|goog.structs.Map)=)}
|
||||
* xhrSender If provided, this will be used to send a report to the
|
||||
* server instead of the default method. The function will be given the URI,
|
||||
* HTTP method request content, and (optionally) request headers to be
|
||||
* added.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setXhrSender = function(xhrSender) {
|
||||
this.xhrSender_ = xhrSender;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the error reporter.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.setup_ = function() {
|
||||
if (goog.userAgent.IE) {
|
||||
// Use "onerror" because caught exceptions in IE don't provide line number.
|
||||
goog.debug.catchErrors(
|
||||
goog.bind(this.handleException, this), false, null);
|
||||
} else {
|
||||
// "onerror" doesn't work with FF2 or Chrome
|
||||
this.errorHandler_ = new goog.debug.ErrorHandler(
|
||||
goog.bind(this.handleException, this));
|
||||
|
||||
this.errorHandler_.protectWindowSetTimeout();
|
||||
this.errorHandler_.protectWindowSetInterval();
|
||||
goog.debug.entryPointRegistry.monitorAll(this.errorHandler_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for caught exceptions. Sends report to the LoggingServlet and
|
||||
* notifies any listeners.
|
||||
*
|
||||
* @param {Object} e The exception.
|
||||
* @param {!Object.<string, string>=} opt_context Context values to optionally
|
||||
* include in the error report.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.handleException = function(e,
|
||||
opt_context) {
|
||||
var error = (/** @type {!Error} */ goog.debug.normalizeErrorObject(e));
|
||||
|
||||
// Construct the context, possibly from the one provided in the argument, and
|
||||
// pass it to the context provider if there is one.
|
||||
var context = opt_context ? goog.object.clone(opt_context) : {};
|
||||
if (this.contextProvider_) {
|
||||
try {
|
||||
this.contextProvider_(error, context);
|
||||
} catch (err) {
|
||||
goog.debug.ErrorReporter.logger_.severe('Context provider threw an ' +
|
||||
'exception: ' + err.message);
|
||||
}
|
||||
}
|
||||
this.sendErrorReport(error.message, error.fileName, error.lineNumber,
|
||||
error.stack, context);
|
||||
|
||||
try {
|
||||
this.dispatchEvent(
|
||||
new goog.debug.ErrorReporter.ExceptionEvent(error, context));
|
||||
} catch (ex) {
|
||||
// Swallow exception to avoid infinite recursion.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends an error report to the logging URL. This will not consult the context
|
||||
* provider, the report will be sent exactly as specified.
|
||||
*
|
||||
* @param {string} message Error description.
|
||||
* @param {string} fileName URL of the JavaScript file with the error.
|
||||
* @param {number} line Line number of the error.
|
||||
* @param {string=} opt_trace Call stack trace of the error.
|
||||
* @param {!Object.<string, string>=} opt_context Context information to include
|
||||
* in the request.
|
||||
*/
|
||||
goog.debug.ErrorReporter.prototype.sendErrorReport =
|
||||
function(message, fileName, line, opt_trace, opt_context) {
|
||||
try {
|
||||
// Create the logging URL.
|
||||
var requestUrl = goog.uri.utils.appendParams(this.handlerUrl_,
|
||||
'script', fileName, 'error', message, 'line', line);
|
||||
var queryMap = {};
|
||||
queryMap['trace'] = opt_trace;
|
||||
|
||||
// Copy context into query data map
|
||||
if (opt_context) {
|
||||
for (var entry in opt_context) {
|
||||
queryMap['context.' + entry] = opt_context[entry];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy query data map into request.
|
||||
var queryData = goog.uri.utils.buildQueryDataFromMap(queryMap);
|
||||
|
||||
// Send the request with the contents of the error.
|
||||
this.xhrSender_(requestUrl, 'POST', queryData, this.extraHeaders_);
|
||||
} catch (e) {
|
||||
var logMessage = goog.string.buildString(
|
||||
'Error occurred in sending an error report.\n\n',
|
||||
'script:', fileName, '\n',
|
||||
'line:', line, '\n',
|
||||
'error:', message, '\n',
|
||||
'trace:', opt_trace);
|
||||
goog.debug.ErrorReporter.logger_.info(logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.ErrorReporter.prototype.disposeInternal = function() {
|
||||
goog.dispose(this.errorHandler_);
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,363 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the FancyWindow class. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
* This is a pretty hacky implementation, aimed at making debugging of large
|
||||
* applications more manageable.
|
||||
*
|
||||
* @see ../demos/debug.html
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.debug.FancyWindow');
|
||||
|
||||
goog.require('goog.debug.DebugWindow');
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.Logger.Level');
|
||||
goog.require('goog.dom.DomHelper');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides a Fancy extension to the DebugWindow class. Allows filtering based
|
||||
* on loggers and levels.
|
||||
*
|
||||
* @param {string=} opt_identifier Idenitifier for this logging class.
|
||||
* @param {string=} opt_prefix Prefix pre-pended to messages.
|
||||
* @constructor
|
||||
* @extends {goog.debug.DebugWindow}
|
||||
*/
|
||||
goog.debug.FancyWindow = function(opt_identifier, opt_prefix) {
|
||||
this.readOptionsFromLocalStorage_();
|
||||
goog.base(this, opt_identifier, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.FancyWindow, goog.debug.DebugWindow);
|
||||
|
||||
|
||||
/**
|
||||
* Constant indicating if we are able to use localStorage to persist filters
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.FancyWindow.HAS_LOCAL_STORE = (function() {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
return !!window['localStorage'].getItem;
|
||||
} catch (e) {}
|
||||
return false;
|
||||
})();
|
||||
|
||||
|
||||
/**
|
||||
* Constant defining the prefix to use when storing log levels
|
||||
* @type {string}
|
||||
*/
|
||||
goog.debug.FancyWindow.LOCAL_STORE_PREFIX = 'fancywindow.sel.';
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.writeBufferToLog = function() {
|
||||
this.lastCall_ = goog.now();
|
||||
if (this.hasActiveWindow()) {
|
||||
var logel = this.dh_.getElement('log');
|
||||
|
||||
// Work out if scrolling is needed before we add the content
|
||||
var scroll =
|
||||
logel.scrollHeight - (logel.scrollTop + logel.offsetHeight) <= 100;
|
||||
|
||||
for (var i = 0; i < this.outputBuffer_.length; i++) {
|
||||
var div = this.dh_.createDom('div', 'logmsg');
|
||||
div.innerHTML = this.outputBuffer_[i];
|
||||
logel.appendChild(div);
|
||||
}
|
||||
this.outputBuffer_.length = 0;
|
||||
this.resizeStuff_();
|
||||
|
||||
if (scroll) {
|
||||
logel.scrollTop = logel.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.writeInitialDocument = function() {
|
||||
if (!this.hasActiveWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var doc = this.win_.document;
|
||||
doc.open();
|
||||
doc.write(this.getHtml_());
|
||||
doc.close();
|
||||
|
||||
(goog.userAgent.IE ? doc.body : this.win_).onresize =
|
||||
goog.bind(this.resizeStuff_, this);
|
||||
|
||||
// Create a dom helper for the logging window
|
||||
this.dh_ = new goog.dom.DomHelper(doc);
|
||||
|
||||
// Don't use events system to reduce dependencies
|
||||
this.dh_.getElement('openbutton').onclick =
|
||||
goog.bind(this.openOptions_, this);
|
||||
this.dh_.getElement('closebutton').onclick =
|
||||
goog.bind(this.closeOptions_, this);
|
||||
this.dh_.getElement('clearbutton').onclick =
|
||||
goog.bind(this.clear_, this);
|
||||
this.dh_.getElement('exitbutton').onclick =
|
||||
goog.bind(this.exit_, this);
|
||||
|
||||
this.writeSavedMessages_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the options menu.
|
||||
* @return {boolean} false.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.openOptions_ = function() {
|
||||
var el = this.dh_.getElement('optionsarea');
|
||||
el.innerHTML = '';
|
||||
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var dh = this.dh_;
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var logger = goog.debug.Logger.getLogger(loggers[i]);
|
||||
var curlevel = logger.getLevel() ? logger.getLevel().name : 'INHERIT';
|
||||
var div = dh.createDom('div', {},
|
||||
this.getDropDown_('sel' + loggers[i], curlevel),
|
||||
dh.createDom('span', {}, loggers[i] || '(root)'));
|
||||
el.appendChild(div);
|
||||
}
|
||||
|
||||
this.dh_.getElement('options').style.display = 'block';
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make a drop down for the log levels.
|
||||
* @param {string} id Logger id.
|
||||
* @param {string} selected What log level is currently selected.
|
||||
* @return {Element} The newly created 'select' DOM element.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.getDropDown_ = function(id, selected) {
|
||||
var dh = this.dh_;
|
||||
var sel = dh.createDom('select', {'id': id});
|
||||
var levels = goog.debug.Logger.Level.PREDEFINED_LEVELS;
|
||||
for (var i = 0; i < levels.length; i++) {
|
||||
var level = levels[i];
|
||||
var option = dh.createDom('option', {}, level.name);
|
||||
if (selected == level.name) {
|
||||
option.selected = true;
|
||||
}
|
||||
sel.appendChild(option);
|
||||
}
|
||||
sel.appendChild(dh.createDom('option',
|
||||
{'selected': selected == 'INHERIT'}, 'INHERIT'));
|
||||
return sel;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Close the options menu.
|
||||
* @return {boolean} The value false.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.closeOptions_ = function() {
|
||||
this.dh_.getElement('options').style.display = 'none';
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var dh = this.dh_;
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var logger = goog.debug.Logger.getLogger(loggers[i]);
|
||||
var sel = dh.getElement('sel' + loggers[i]);
|
||||
var level = sel.options[sel.selectedIndex].text;
|
||||
if (level == 'INHERIT') {
|
||||
logger.setLevel(null);
|
||||
} else {
|
||||
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(level));
|
||||
}
|
||||
}
|
||||
this.writeOptionsToLocalStorage_();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resize the lof elements
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.resizeStuff_ = function() {
|
||||
var dh = this.dh_;
|
||||
var logel = dh.getElement('log');
|
||||
var headel = dh.getElement('head');
|
||||
logel.style.top = headel.offsetHeight + 'px';
|
||||
logel.style.height = (dh.getDocument().body.offsetHeight -
|
||||
headel.offsetHeight - (goog.userAgent.IE ? 4 : 0)) + 'px';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the user clicking the exit button, disabled the debug window and
|
||||
* closes the popup.
|
||||
* @param {Event} e Event object.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.exit_ = function(e) {
|
||||
this.setEnabled(false);
|
||||
if (this.win_) {
|
||||
this.win_.close();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FancyWindow.prototype.getStyleRules = function() {
|
||||
return goog.base(this, 'getStyleRules') +
|
||||
'html,body{height:100%;width:100%;margin:0px;padding:0px;' +
|
||||
'background-color:#FFF;overflow:hidden}' +
|
||||
'*{}' +
|
||||
'.logmsg{border-bottom:1px solid #CCC;padding:2px;font:90% monospace}' +
|
||||
'#head{position:absolute;width:100%;font:x-small arial;' +
|
||||
'border-bottom:2px solid #999;background-color:#EEE;}' +
|
||||
'#head p{margin:0px 5px;}' +
|
||||
'#log{position:absolute;width:100%;background-color:#FFF;}' +
|
||||
'#options{position:absolute;right:0px;width:50%;height:100%;' +
|
||||
'border-left:1px solid #999;background-color:#DDD;display:none;' +
|
||||
'padding-left: 5px;font:normal small arial;overflow:auto;}' +
|
||||
'#openbutton,#closebutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:5px;font:x-small arial;}' +
|
||||
'#clearbutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:80px;font:x-small arial;}' +
|
||||
'#exitbutton{text-decoration:underline;color:#00F;cursor:' +
|
||||
'pointer;position:absolute;top:0px;right:50px;font:x-small arial;}' +
|
||||
'select{font:x-small arial;margin-right:10px;}' +
|
||||
'hr{border:0;height:5px;background-color:#8c8;color:#8c8;}';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the default HTML for the debug window
|
||||
* @return {string} Html.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.getHtml_ = function() {
|
||||
return '' +
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"' +
|
||||
'"http://www.w3.org/TR/html4/loose.dtd">' +
|
||||
'<html><head><title>Logging: ' + this.identifier_ + '</title>' +
|
||||
'<style>' + this.getStyleRules() + '</style>' +
|
||||
'</head><body>' +
|
||||
'<div id="log" style="overflow:auto"></div>' +
|
||||
'<div id="head">' +
|
||||
'<p><b>Logging: ' + this.identifier_ + '</b></p><p>' +
|
||||
this.welcomeMessage + '</p>' +
|
||||
'<span id="clearbutton">clear</span>' +
|
||||
'<span id="exitbutton">exit</span>' +
|
||||
'<span id="openbutton">options</span>' +
|
||||
'</div>' +
|
||||
'<div id="options">' +
|
||||
'<big><b>Options:</b></big>' +
|
||||
'<div id="optionsarea"></div>' +
|
||||
'<span id="closebutton">save and close</span>' +
|
||||
'</div>' +
|
||||
'</body></html>';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write logger levels to localStorage if possible.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.writeOptionsToLocalStorage_ = function() {
|
||||
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
|
||||
return;
|
||||
}
|
||||
var loggers = goog.debug.FancyWindow.getLoggers_();
|
||||
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
|
||||
for (var i = 0; i < loggers.length; i++) {
|
||||
var key = goog.debug.FancyWindow.LOCAL_STORE_PREFIX + loggers[i];
|
||||
var level = goog.debug.Logger.getLogger(loggers[i]).getLevel();
|
||||
if (key in storedKeys) {
|
||||
if (!level) {
|
||||
window.localStorage.removeItem(key);
|
||||
} else if (window.localStorage.getItem(key) != level.name) {
|
||||
window.localStorage.setItem(key, level.name);
|
||||
}
|
||||
} else if (level) {
|
||||
window.localStorage.setItem(key, level.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sync logger levels with any values stored in localStorage.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.prototype.readOptionsFromLocalStorage_ = function() {
|
||||
if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
|
||||
return;
|
||||
}
|
||||
var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
|
||||
for (var key in storedKeys) {
|
||||
var loggerName = key.replace(goog.debug.FancyWindow.LOCAL_STORE_PREFIX, '');
|
||||
var logger = goog.debug.Logger.getLogger(loggerName);
|
||||
var curLevel = logger.getLevel();
|
||||
var storedLevel = window.localStorage.getItem(key).toString();
|
||||
if (!curLevel || curLevel.toString() != storedLevel) {
|
||||
logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(storedLevel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to create a list of locally stored keys. Used to avoid
|
||||
* expensive localStorage.getItem() calls.
|
||||
* @return {Object} List of keys.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.getStoredKeys_ = function() {
|
||||
var storedKeys = {};
|
||||
for (var i = 0, len = window.localStorage.length; i < len; i++) {
|
||||
var key = window.localStorage.key(i);
|
||||
if (key != null && goog.string.startsWith(
|
||||
key, goog.debug.FancyWindow.LOCAL_STORE_PREFIX)) {
|
||||
storedKeys[key] = true;
|
||||
}
|
||||
}
|
||||
return storedKeys;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a sorted array of all the loggers registered
|
||||
* @return {Array} Array of logger idents, e.g. goog.net.XhrIo.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FancyWindow.getLoggers_ = function() {
|
||||
var loggers = goog.object.getKeys(goog.debug.LogManager.getLoggers());
|
||||
loggers.sort();
|
||||
return loggers;
|
||||
};
|
||||
@@ -0,0 +1,317 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of various formatters for logging. Please minimize
|
||||
* dependencies this file has on other closure classes as any dependency it
|
||||
* takes won't be able to use the logging infrastructure.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Formatter');
|
||||
goog.provide('goog.debug.HtmlFormatter');
|
||||
goog.provide('goog.debug.TextFormatter');
|
||||
|
||||
goog.require('goog.debug.RelativeTimeProvider');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for Formatters. A Formatter is used to format a LogRecord into
|
||||
* something that can be displayed to the user.
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.Formatter = function(opt_prefix) {
|
||||
this.prefix_ = opt_prefix || '';
|
||||
|
||||
/**
|
||||
* A provider that returns the relative start time.
|
||||
* @type {goog.debug.RelativeTimeProvider}
|
||||
* @private
|
||||
*/
|
||||
this.startTimeProvider_ =
|
||||
goog.debug.RelativeTimeProvider.getDefaultInstance();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show absolute time in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showAbsoluteTime = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show relative time in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showRelativeTime = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger name in the DebugWindow.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showLoggerName = true;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger exception text.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showExceptionText = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the severity level.
|
||||
* @type {boolean}
|
||||
*/
|
||||
goog.debug.Formatter.prototype.showSeverityLevel = false;
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record.
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the start time provider. By default, this is the default instance
|
||||
* but can be changed.
|
||||
* @param {goog.debug.RelativeTimeProvider} provider The provider to use.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
|
||||
this.startTimeProvider_ = provider;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the start time provider. By default, this is the default instance
|
||||
* but can be changed.
|
||||
* @return {goog.debug.RelativeTimeProvider} The start time provider.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.getStartTimeProvider = function() {
|
||||
return this.startTimeProvider_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the start relative time.
|
||||
*/
|
||||
goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
|
||||
this.startTimeProvider_.reset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string for the time/date of the LogRecord.
|
||||
* @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
|
||||
* @return {string} A string representation of the time/date of the LogRecord.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
|
||||
var time = new Date(logRecord.getMillis());
|
||||
return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
|
||||
goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
|
||||
goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
|
||||
goog.debug.Formatter.getTwoDigitString_(
|
||||
Math.floor(time.getMilliseconds() / 10));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number as a two-digit string, meaning it prepends a 0 if the
|
||||
* number if less than 10.
|
||||
* @param {number} n The number to format.
|
||||
* @return {string} A two-digit string representation of {@code n}.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getTwoDigitString_ = function(n) {
|
||||
if (n < 10) {
|
||||
return '0' + n;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string for the number of seconds relative to the start time.
|
||||
* Prepads with spaces so that anything less than 1000 seconds takes up the
|
||||
* same number of characters for better formatting.
|
||||
* @param {goog.debug.LogRecord} logRecord The log to compare time to.
|
||||
* @param {number} relativeTimeStart The start time to compare to.
|
||||
* @return {string} The number of seconds of the LogRecord relative to the
|
||||
* start time.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Formatter.getRelativeTime_ = function(logRecord,
|
||||
relativeTimeStart) {
|
||||
var ms = logRecord.getMillis() - relativeTimeStart;
|
||||
var sec = ms / 1000;
|
||||
var str = sec.toFixed(3);
|
||||
|
||||
var spacesToPrepend = 0;
|
||||
if (sec < 1) {
|
||||
spacesToPrepend = 2;
|
||||
} else {
|
||||
while (sec < 100) {
|
||||
spacesToPrepend++;
|
||||
sec *= 10;
|
||||
}
|
||||
}
|
||||
while (spacesToPrepend-- > 0) {
|
||||
str = ' ' + str;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatter that returns formatted html. See formatRecord for the classes
|
||||
* it uses for various types of formatted output.
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Formatter}
|
||||
*/
|
||||
goog.debug.HtmlFormatter = function(opt_prefix) {
|
||||
goog.debug.Formatter.call(this, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
|
||||
|
||||
|
||||
/**
|
||||
* Whether to show the logger exception text
|
||||
* @type {boolean}
|
||||
* @override
|
||||
*/
|
||||
goog.debug.HtmlFormatter.prototype.showExceptionText = true;
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string as html.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
|
||||
var className;
|
||||
switch (logRecord.getLevel().value) {
|
||||
case goog.debug.Logger.Level.SHOUT.value:
|
||||
className = 'dbg-sh';
|
||||
break;
|
||||
case goog.debug.Logger.Level.SEVERE.value:
|
||||
className = 'dbg-sev';
|
||||
break;
|
||||
case goog.debug.Logger.Level.WARNING.value:
|
||||
className = 'dbg-w';
|
||||
break;
|
||||
case goog.debug.Logger.Level.INFO.value:
|
||||
className = 'dbg-i';
|
||||
break;
|
||||
case goog.debug.Logger.Level.FINE.value:
|
||||
default:
|
||||
className = 'dbg-f';
|
||||
break;
|
||||
}
|
||||
|
||||
// Build message html
|
||||
var sb = [];
|
||||
sb.push(this.prefix_, ' ');
|
||||
if (this.showAbsoluteTime) {
|
||||
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
|
||||
}
|
||||
if (this.showRelativeTime) {
|
||||
sb.push('[',
|
||||
goog.string.whitespaceEscape(
|
||||
goog.debug.Formatter.getRelativeTime_(logRecord,
|
||||
this.startTimeProvider_.get())),
|
||||
's] ');
|
||||
}
|
||||
|
||||
if (this.showLoggerName) {
|
||||
sb.push('[', goog.string.htmlEscape(logRecord.getLoggerName()), '] ');
|
||||
}
|
||||
sb.push('<span class="', className, '">',
|
||||
goog.string.newLineToBr(goog.string.whitespaceEscape(
|
||||
goog.string.htmlEscape(logRecord.getMessage()))));
|
||||
|
||||
if (this.showExceptionText && logRecord.getException()) {
|
||||
sb.push('<br>',
|
||||
goog.string.newLineToBr(goog.string.whitespaceEscape(
|
||||
logRecord.getExceptionText() || '')));
|
||||
}
|
||||
sb.push('</span><br>');
|
||||
|
||||
// If the logger is enabled, open window and write html message to log
|
||||
// otherwise save it
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatter that returns formatted plain text
|
||||
*
|
||||
* @param {string=} opt_prefix The prefix to place before text records.
|
||||
* @constructor
|
||||
* @extends {goog.debug.Formatter}
|
||||
*/
|
||||
goog.debug.TextFormatter = function(opt_prefix) {
|
||||
goog.debug.Formatter.call(this, opt_prefix);
|
||||
};
|
||||
goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
|
||||
|
||||
|
||||
/**
|
||||
* Formats a record as text
|
||||
* @param {goog.debug.LogRecord} logRecord the logRecord to format.
|
||||
* @return {string} The formatted string.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
|
||||
// Build message html
|
||||
var sb = [];
|
||||
sb.push(this.prefix_, ' ');
|
||||
if (this.showAbsoluteTime) {
|
||||
sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
|
||||
}
|
||||
if (this.showRelativeTime) {
|
||||
sb.push('[', goog.debug.Formatter.getRelativeTime_(logRecord,
|
||||
this.startTimeProvider_.get()), 's] ');
|
||||
}
|
||||
|
||||
if (this.showLoggerName) {
|
||||
sb.push('[', logRecord.getLoggerName(), '] ');
|
||||
}
|
||||
if (this.showSeverityLevel) {
|
||||
sb.push('[', logRecord.getLevel().name, '] ');
|
||||
}
|
||||
sb.push(logRecord.getMessage(), '\n');
|
||||
if (this.showExceptionText && logRecord.getException()) {
|
||||
sb.push(logRecord.getExceptionText(), '\n');
|
||||
}
|
||||
// If the logger is enabled, open window and write html message to log
|
||||
// otherwise save it
|
||||
return sb.join('');
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Displays frames per second (FPS) for the current window.
|
||||
* Only supported in browsers that support requestAnimationFrame.
|
||||
* See: https://developer.mozilla.org/en/DOM/window.requestAnimationFrame.
|
||||
*
|
||||
* @see ../demos/fpsdisplay.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.FpsDisplay');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.AnimationDelay');
|
||||
goog.require('goog.ui.Component');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Displays frames per seconds that the window this component is
|
||||
* rendered in is animating at.
|
||||
*
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper An optional dom helper.
|
||||
* @constructor
|
||||
* @extends {goog.ui.Component}
|
||||
*/
|
||||
goog.debug.FpsDisplay = function(opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
};
|
||||
goog.inherits(goog.debug.FpsDisplay, goog.ui.Component);
|
||||
|
||||
|
||||
/**
|
||||
* CSS class for the FPS display.
|
||||
*/
|
||||
goog.debug.FpsDisplay.CSS = goog.getCssName('goog-fps-display');
|
||||
|
||||
|
||||
/**
|
||||
* The number of samples per FPS report.
|
||||
*/
|
||||
goog.debug.FpsDisplay.SAMPLES = 10;
|
||||
|
||||
|
||||
/**
|
||||
* The current animation.
|
||||
* @type {goog.debug.FpsDisplay.FpsAnimation_}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.animation_ = null;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.createDom = function() {
|
||||
this.setElementInternal(
|
||||
this.getDomHelper().createDom('div', goog.debug.FpsDisplay.CSS));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.enterDocument = function() {
|
||||
goog.base(this, 'enterDocument');
|
||||
this.animation_ = new goog.debug.FpsDisplay.FpsAnimation_(this.getElement());
|
||||
this.delay_ = new goog.async.AnimationDelay(
|
||||
this.handleDelay_, this.getDomHelper().getWindow(), this);
|
||||
this.delay_.start();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} now The current time.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.handleDelay_ = function(now) {
|
||||
if (this.isInDocument()) {
|
||||
this.animation_.onAnimationFrame(now);
|
||||
this.delay_.start();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.debug.FpsDisplay.prototype.exitDocument = function() {
|
||||
goog.base(this, 'exitDocument');
|
||||
this.animation_ = null;
|
||||
goog.dispose(this.delay_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The average frames per second.
|
||||
*/
|
||||
goog.debug.FpsDisplay.prototype.getFps = function() {
|
||||
goog.asserts.assert(
|
||||
this.isInDocument(), 'Render the FPS display before querying FPS');
|
||||
return this.animation_.lastFps_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {Element} elem An element to hold the FPS count.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_ = function(elem) {
|
||||
/**
|
||||
* An element to hold the current FPS rate.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.element_ = elem;
|
||||
|
||||
/**
|
||||
* The number of frames observed so far.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.frameNumber_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The last time which we reported FPS at.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastTime_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The last average FPS.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.lastFps_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} now The current time.
|
||||
*/
|
||||
goog.debug.FpsDisplay.FpsAnimation_.prototype.onAnimationFrame = function(now) {
|
||||
var SAMPLES = goog.debug.FpsDisplay.SAMPLES;
|
||||
if (this.frameNumber_ % SAMPLES == 0) {
|
||||
this.lastFps_ = Math.round((1000 * SAMPLES) / (now - this.lastTime_));
|
||||
this.element_.innerHTML = this.lastFps_;
|
||||
this.lastTime_ = now;
|
||||
}
|
||||
this.frameNumber_++;
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the GcDiagnostics class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.GcDiagnostics');
|
||||
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.debug.Trace');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class used for singleton goog.debug.GcDiagnostics. Used to hook into
|
||||
* the L2 ActiveX controller to profile garbage collection information in IE.
|
||||
* Can be used in combination with tracers (goog.debug.Trace), to provide object
|
||||
* allocation counts from within the tracers or used alone by invoking start and
|
||||
* stop.
|
||||
*
|
||||
* See http://go/l2binary for the install.
|
||||
* TODO(user): Move the L2 installer somewhere more general.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.GcDiagnostics_ = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Install the GcDiagnostics tool.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.install = function() {
|
||||
if (goog.userAgent.IE) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var l2Helper = new ActiveXObject('L2.NativeHelper');
|
||||
|
||||
// If using tracers, use the higher precision timer provided by L2.
|
||||
if (goog.debug.Trace_) {
|
||||
goog.debug.Trace_.now = function() {
|
||||
return l2Helper['getMilliSeconds']();
|
||||
};
|
||||
}
|
||||
|
||||
if (l2Helper['gcTracer']) {
|
||||
l2Helper['gcTracer']['installGcTracing']();
|
||||
this.gcTracer_ = l2Helper['gcTracer'];
|
||||
if (goog.debug.Trace) {
|
||||
// If tracers are in use, register the gcTracer so that per tracer
|
||||
// allocations are recorded.
|
||||
goog.debug.Trace.setGcTracer(this.gcTracer_);
|
||||
}
|
||||
}
|
||||
this.logger_.info('Installed L2 native helper');
|
||||
} catch (e) {
|
||||
this.logger_.info('Failed to install L2 native helper: ' + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logger for the gcDiagnotics
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.logger_ =
|
||||
goog.debug.Logger.getLogger('goog.debug.GcDiagnostics');
|
||||
|
||||
|
||||
/**
|
||||
* Starts recording garbage collection information. If a trace is already in
|
||||
* progress, it is ended.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.start = function() {
|
||||
if (this.gcTracer_) {
|
||||
if (this.gcTracer_['isTracing']()) {
|
||||
this.gcTracer_['endGcTracing']();
|
||||
}
|
||||
this.gcTracer_['startGcTracing']();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops recording garbage collection information. Logs details on the garbage
|
||||
* collections that occurred between start and stop. If tracers are in use,
|
||||
* adds comments where each GC occurs.
|
||||
*/
|
||||
goog.debug.GcDiagnostics_.prototype.stop = function() {
|
||||
if (this.gcTracer_ && this.gcTracer_['isTracing']()) {
|
||||
var gcTracer = this.gcTracer_;
|
||||
this.gcTracer_['endGcTracing']();
|
||||
|
||||
var numGCs = gcTracer['getNumTraces']();
|
||||
this.logger_.info('*********GC TRACE*********');
|
||||
this.logger_.info('GC ran ' + numGCs + ' times.');
|
||||
var totalTime = 0;
|
||||
for (var i = 0; i < numGCs; i++) {
|
||||
var trace = gcTracer['getTrace'](i);
|
||||
|
||||
var msStart = trace['gcStartTime'];
|
||||
var msElapsed = trace['gcElapsedTime'];
|
||||
|
||||
var msRounded = Math.round(msElapsed * 10) / 10;
|
||||
var s = 'GC ' + i + ': ' + msRounded + ' ms, ' +
|
||||
'numVValAlloc=' + trace['numVValAlloc'] + ', ' +
|
||||
'numVarAlloc=' + trace['numVarAlloc'] + ', ' +
|
||||
'numBytesSysAlloc=' + trace['numBytesSysAlloc'];
|
||||
if (goog.debug.Trace) {
|
||||
goog.debug.Trace.addComment(s, null, msStart);
|
||||
}
|
||||
this.logger_.info(s);
|
||||
totalTime += msElapsed;
|
||||
}
|
||||
if (goog.debug.Trace) {
|
||||
goog.debug.Trace.addComment('Total GC time was ' + totalTime + ' ms.');
|
||||
}
|
||||
this.logger_.info('Total GC time was ' + totalTime + ' ms.');
|
||||
this.logger_.info('*********GC TRACE*********');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton GcDiagnostics object
|
||||
* @type {goog.debug.GcDiagnostics_}
|
||||
*/
|
||||
goog.debug.GcDiagnostics = new goog.debug.GcDiagnostics_();
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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
|
||||
*/
|
||||
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.debug.LogBuffer.CAPACITY = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The array to store the records.
|
||||
* @type {!Array.<!goog.debug.LogRecord|undefined>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.buffer_;
|
||||
|
||||
|
||||
/**
|
||||
* The index of the most recently added record or -1 if there are no records.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.curIndex_;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the buffer is at capacity.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.isFull_;
|
||||
|
||||
|
||||
/**
|
||||
* Adds a log record to the buffer, possibly overwriting the oldest record.
|
||||
* @param {goog.debug.Logger.Level} level One of the level identifiers.
|
||||
* @param {string} msg The string message.
|
||||
* @param {string} loggerName The name of the source logger.
|
||||
* @return {!goog.debug.LogRecord} The log record.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
|
||||
var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
|
||||
this.curIndex_ = curIndex;
|
||||
if (this.isFull_) {
|
||||
var ret = this.buffer_[curIndex];
|
||||
ret.reset(level, msg, loggerName);
|
||||
return ret;
|
||||
}
|
||||
this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
|
||||
return this.buffer_[curIndex] =
|
||||
new goog.debug.LogRecord(level, msg, loggerName);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the log buffer is enabled.
|
||||
*/
|
||||
goog.debug.LogBuffer.isBufferingEnabled = function() {
|
||||
return goog.debug.LogBuffer.CAPACITY > 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes all buffered log records.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.clear = function() {
|
||||
this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
|
||||
this.curIndex_ = -1;
|
||||
this.isFull_ = false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calls the given function for each buffered log record, starting with the
|
||||
* oldest one.
|
||||
* @param {function(!goog.debug.LogRecord)} func The function to call.
|
||||
*/
|
||||
goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
|
||||
var buffer = this.buffer_;
|
||||
// Corner case: no records.
|
||||
if (!buffer[0]) {
|
||||
return;
|
||||
}
|
||||
var curIndex = this.curIndex_;
|
||||
var i = this.isFull_ ? curIndex : -1;
|
||||
do {
|
||||
i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
|
||||
func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
|
||||
} while (i != curIndex);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,823 @@
|
||||
// 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.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');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
goog.debug.Logger = function(name) {
|
||||
/**
|
||||
* Name of the Logger. Generally a dot-separated namespace
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parent Logger.
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.parent_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Level that this logger only filters above. Null indicates it should
|
||||
* inherit from the parent.
|
||||
* @type {goog.debug.Logger.Level}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.level_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Map of children loggers. The keys are the leaf names of the children and
|
||||
* the values are the child loggers.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.children_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Handlers that are listening to this logger.
|
||||
* @type {Array.<Function>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.prototype.handlers_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @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.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
|
||||
*/
|
||||
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>Number.MAX_VALUE</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>Number.MIN_VALUE</CODE>.
|
||||
* @type {!goog.debug.Logger.Level}
|
||||
*/
|
||||
goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
|
||||
|
||||
|
||||
/**
|
||||
* The predefined levels.
|
||||
* @type {!Array.<!goog.debug.Logger.Level>}
|
||||
* @final
|
||||
*/
|
||||
goog.debug.Logger.Level.PREDEFINED_LEVELS = [
|
||||
goog.debug.Logger.Level.OFF,
|
||||
goog.debug.Logger.Level.SHOUT,
|
||||
goog.debug.Logger.Level.SEVERE,
|
||||
goog.debug.Logger.Level.WARNING,
|
||||
goog.debug.Logger.Level.INFO,
|
||||
goog.debug.Logger.Level.CONFIG,
|
||||
goog.debug.Logger.Level.FINE,
|
||||
goog.debug.Logger.Level.FINER,
|
||||
goog.debug.Logger.Level.FINEST,
|
||||
goog.debug.Logger.Level.ALL];
|
||||
|
||||
|
||||
/**
|
||||
* A lookup map used to find the level object based on the name or value of
|
||||
* the level object.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the predefined levels cache and populates it.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_ = {};
|
||||
for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
|
||||
i++) {
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
|
||||
goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the predefined level with the given name.
|
||||
* @param {string} name The name of the level.
|
||||
* @return {goog.debug.Logger.Level} The level, or null if none found.
|
||||
*/
|
||||
goog.debug.Logger.Level.getPredefinedLevel = function(name) {
|
||||
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_();
|
||||
}
|
||||
|
||||
return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the highest predefined level <= #value.
|
||||
* @param {number} value Level value.
|
||||
* @return {goog.debug.Logger.Level} The level, or null if none found.
|
||||
*/
|
||||
goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
|
||||
if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
goog.debug.Logger.Level.createPredefinedLevelsCache_();
|
||||
}
|
||||
|
||||
if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
|
||||
return goog.debug.Logger.Level.predefinedLevelsCache_[value];
|
||||
}
|
||||
|
||||
for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
|
||||
var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
|
||||
if (level.value <= value) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Find or create 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.
|
||||
*/
|
||||
goog.debug.Logger.getLogger = function(name) {
|
||||
return goog.debug.LogManager.getLogger(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logs a message to profiling tools, if available.
|
||||
* {@see http://code.google.com/webtoolkit/speedtracer/logging-api.html}
|
||||
* {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
|
||||
* @param {string} msg The message to log.
|
||||
*/
|
||||
goog.debug.Logger.logToProfilers = function(msg) {
|
||||
// Using goog.global, as loggers might be used in window-less contexts.
|
||||
if (goog.global['console']) {
|
||||
if (goog.global['console']['timeStamp']) {
|
||||
// Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
|
||||
goog.global['console']['timeStamp'](msg);
|
||||
} else if (goog.global['console']['markTimeline']) {
|
||||
// TODO(user): markTimeline is deprecated. Drop this else clause entirely
|
||||
// after Chrome M14 hits stable.
|
||||
goog.global['console']['markTimeline'](msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (goog.global['msWriteProfilerMark']) {
|
||||
// Logs a message to the Microsoft profiler
|
||||
goog.global['msWriteProfilerMark'](msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the name of this logger.
|
||||
* @return {string} The name of this logger.
|
||||
*/
|
||||
goog.debug.Logger.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler to the logger. This doesn't use the event system because
|
||||
* we want to be able to add logging to the event system.
|
||||
* @param {Function} handler Handler function to add.
|
||||
*/
|
||||
goog.debug.Logger.prototype.addHandler = function(handler) {
|
||||
if (goog.debug.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) {
|
||||
var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
|
||||
goog.debug.Logger.rootHandlers_;
|
||||
return !!handlers && goog.array.remove(handlers, handler);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.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 this.level_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check 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 level.value >= this.getEffectiveLevel().value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @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 (this.isLoggable(level)) {
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
logRecord.setExceptionText(
|
||||
goog.debug.exposeException(opt_exception, arguments.callee.caller));
|
||||
}
|
||||
return logRecord;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.info = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.config = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 {string} msg The string message.
|
||||
* @param {Error=} opt_exception An exception associated with the message.
|
||||
*/
|
||||
goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
|
||||
this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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 (this.isLoggable(logRecord.getLevel())) {
|
||||
this.doLogRecord_(logRecord);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Log 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.
|
||||
*/
|
||||
goog.debug.LogManager = {};
|
||||
|
||||
|
||||
/**
|
||||
* Map of logger names to logger objects
|
||||
*
|
||||
* @type {!Object}
|
||||
* @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;
|
||||
|
||||
|
||||
/**
|
||||
* Initialize 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.LogManager.loggers_[''] = goog.debug.LogManager.rootLogger_;
|
||||
goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns all the loggers
|
||||
* @return {!Object} 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_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Method to find a named logger.
|
||||
*
|
||||
* @param {string} name A name for the logger. This should be a dot-separated
|
||||
* name and should normally be based on the package name or class name of the
|
||||
* subsystem, such as goog.net.BrowserChannel.
|
||||
* @return {!goog.debug.Logger} The named logger.
|
||||
*/
|
||||
goog.debug.LogManager.getLogger = function(name) {
|
||||
goog.debug.LogManager.initialize();
|
||||
var ret = goog.debug.LogManager.loggers_[name];
|
||||
return ret || goog.debug.LogManager.createLogger_(name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a function that can be passed to goog.debug.catchErrors. The function
|
||||
* will log all reported errors using the given logger.
|
||||
* @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
|
||||
* Defaults to the root logger.
|
||||
* @return {function(Object)} The created function.
|
||||
*/
|
||||
goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
|
||||
return function(info) {
|
||||
var logger = opt_logger || goog.debug.LogManager.getRoot();
|
||||
logger.severe('Error: ' + info.message + ' (' + info.fileName +
|
||||
' @ Line: ' + info.line + ')');
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the named logger. Will also create the parents of the named logger
|
||||
* if they don't yet exist.
|
||||
* @param {string} name The name of the logger.
|
||||
* @return {!goog.debug.Logger} The named logger.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogManager.createLogger_ = function(name) {
|
||||
// find parent logger
|
||||
var logger = new goog.debug.Logger(name);
|
||||
if (goog.debug.Logger.ENABLE_HIERARCHY) {
|
||||
var lastDotIndex = name.lastIndexOf('.');
|
||||
var parentName = name.substr(0, lastDotIndex);
|
||||
var leafName = name.substr(lastDotIndex + 1);
|
||||
var parentLogger = goog.debug.LogManager.getLogger(parentName);
|
||||
|
||||
// tell the parent about the child and the child about the parent
|
||||
parentLogger.addChild_(leafName, logger);
|
||||
logger.setParent_(parentLogger);
|
||||
}
|
||||
|
||||
goog.debug.LogManager.loggers_[name] = logger;
|
||||
return logger;
|
||||
};
|
||||
@@ -0,0 +1,271 @@
|
||||
// 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;
|
||||
|
||||
|
||||
/**
|
||||
* Exception text associated with the record
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.exceptionText_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Whether to enable log sequence numbers.
|
||||
*/
|
||||
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_;
|
||||
delete this.exceptionText_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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 exception text that is part of the log record.
|
||||
*
|
||||
* @return {?string} Exception text.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getExceptionText = function() {
|
||||
return this.exceptionText_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the exception text that is part of the log record.
|
||||
*
|
||||
* @param {string} text The exception text.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setExceptionText = function(text) {
|
||||
this.exceptionText_ = text;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the source Logger's name.
|
||||
*
|
||||
* @param {string} loggerName source logger name (may be null).
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
|
||||
this.loggerName_ = loggerName;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the logging message level, for example Level.SEVERE.
|
||||
* @return {goog.debug.Logger.Level} the logging message level.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getLevel = function() {
|
||||
return this.level_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the logging message level, for example Level.SEVERE.
|
||||
* @param {goog.debug.Logger.Level} level the logging message level.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setLevel = function(level) {
|
||||
this.level_ = level;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the "raw" log message, before localization or formatting.
|
||||
*
|
||||
* @return {string} the raw message string.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getMessage = function() {
|
||||
return this.msg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the "raw" log message, before localization or formatting.
|
||||
*
|
||||
* @param {string} msg the raw message string.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setMessage = function(msg) {
|
||||
this.msg_ = msg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get event time in milliseconds since 1970.
|
||||
*
|
||||
* @return {number} event time in millis since 1970.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getMillis = function() {
|
||||
return this.time_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set event time in milliseconds since 1970.
|
||||
*
|
||||
* @param {number} time event time in millis since 1970.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.setMillis = function(time) {
|
||||
this.time_ = time;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the sequence number.
|
||||
* <p>
|
||||
* Sequence numbers are normally assigned in the LogRecord
|
||||
* constructor, which assigns unique sequence numbers to
|
||||
* each new LogRecord in increasing order.
|
||||
* @return {number} the sequence number.
|
||||
*/
|
||||
goog.debug.LogRecord.prototype.getSequenceNumber = function() {
|
||||
return this.sequenceNumber_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Static methods for serializing and deserializing log
|
||||
* messages. These methods are deliberately kept separate from logrecord.js
|
||||
* and logger.js because they add dependencies on goog.json and goog.object.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.logRecordSerializer');
|
||||
|
||||
goog.require('goog.debug.LogRecord');
|
||||
goog.require('goog.debug.Logger.Level');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of object keys used when serializing a log message.
|
||||
* @enum {string}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.Param_ = {
|
||||
TIME: 't',
|
||||
LEVEL_NAME: 'ln',
|
||||
LEVEL_VALUE: 'lv',
|
||||
MSG: 'm',
|
||||
LOGGER_NAME: 'n',
|
||||
SEQUENCE_NUMBER: 's',
|
||||
EXCEPTION: 'e',
|
||||
EXCEPTION_TEXT: 'et'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Serializes a LogRecord to a JSON string. Note that any associated
|
||||
* exception is likely to be lost.
|
||||
* @param {goog.debug.LogRecord} record The record to serialize.
|
||||
* @return {string} Serialized JSON string of the log message.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.serialize = function(record) {
|
||||
var param = goog.debug.logRecordSerializer.Param_;
|
||||
return goog.json.serialize(goog.object.create(
|
||||
param.TIME, record.getMillis(),
|
||||
param.LEVEL_NAME, record.getLevel().name,
|
||||
param.LEVEL_VALUE, record.getLevel().value,
|
||||
param.MSG, record.getMessage(),
|
||||
param.LOGGER_NAME, record.getLoggerName(),
|
||||
param.SEQUENCE_NUMBER, record.getSequenceNumber(),
|
||||
param.EXCEPTION, record.getException(),
|
||||
param.EXCEPTION_TEXT, record.getExceptionText()));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes a JSON-serialized LogRecord.
|
||||
* @param {string} s The JSON serialized record.
|
||||
* @return {!goog.debug.LogRecord} The deserialized record.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.parse = function(s) {
|
||||
return goog.debug.logRecordSerializer.reconstitute_(goog.json.parse(s));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes a JSON-serialized LogRecord. Use this only if you're
|
||||
* naive enough to blindly trust any JSON formatted input that comes
|
||||
* your way.
|
||||
* @param {string} s The JSON serialized record.
|
||||
* @return {!goog.debug.LogRecord} The deserialized record.
|
||||
*/
|
||||
goog.debug.logRecordSerializer.unsafeParse = function(s) {
|
||||
return goog.debug.logRecordSerializer.reconstitute_(goog.json.unsafeParse(s));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Common reconsitution method for for parse and unsafeParse.
|
||||
* @param {Object} o The JSON object.
|
||||
* @return {!goog.debug.LogRecord} The reconstituted record.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.reconstitute_ = function(o) {
|
||||
var param = goog.debug.logRecordSerializer.Param_;
|
||||
var level = goog.debug.logRecordSerializer.getLevel_(
|
||||
o[param.LEVEL_NAME], o[param.LEVEL_VALUE]);
|
||||
|
||||
var ret = new goog.debug.LogRecord(level, o[param.MSG],
|
||||
o[param.LOGGER_NAME], o[param.TIME], o[param.SEQUENCE_NUMBER]);
|
||||
ret.setException(o[param.EXCEPTION]);
|
||||
ret.setExceptionText(o[param.EXCEPTION_TEXT]);
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} name The name of the log level to return.
|
||||
* @param {number} value The numeric value of the log level to return.
|
||||
* @return {goog.debug.Logger.Level} Returns a goog.debug.Logger.Level with
|
||||
* the specified name and value. If the name and value match a predefined
|
||||
* log level, that instance will be returned, otherwise a new one will be
|
||||
* created.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.logRecordSerializer.getLevel_ = function(name, value) {
|
||||
var level = goog.debug.Logger.Level.getPredefinedLevel(name);
|
||||
return level && level.value == value ?
|
||||
level : new goog.debug.Logger.Level(name, value);
|
||||
};
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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 JavaScript reflection tools. They should only be used for
|
||||
* debugging non-compiled code or tests, because there is no guarantee that
|
||||
* they work consistently in all browsers.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.reflect');
|
||||
|
||||
|
||||
/**
|
||||
* Maps the unique id of the known constructors to their full names.
|
||||
* Initialized lazily.
|
||||
* @type {Object.<number, string>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.reflect.typeMap_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* List of all known constructors. Initialized lazily.
|
||||
* @type {Array.<!Function>}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.reflect.constructors_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Copy of {@code Object.prototype.toString} to use if it is overridden later.
|
||||
* Although saving the original {@code toString} somewhat protects against
|
||||
* third-party libraries which touch {@code Object.prototype}, the actual goal
|
||||
* of this assignment is to allow overriding that method, thus more debug
|
||||
* information can be exposed about objects.
|
||||
* See {@link goog.debug.reflect.typeOf}.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.reflect.toString_ = Object.prototype.toString;
|
||||
|
||||
|
||||
/**
|
||||
* Registers a type which will be recognized by goog.debug.reflect.typeOf.
|
||||
* @param {string} name Full name of the type.
|
||||
* @param {!Function} ctor The constructor.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.reflect.registerType_ = function(name, ctor) {
|
||||
goog.debug.reflect.constructors_.push(ctor);
|
||||
goog.debug.reflect.typeMap_[goog.getUid(ctor)] = name;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds all known constructors to the type registry.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.reflect.init_ = function() {
|
||||
if (goog.debug.reflect.typeMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
goog.debug.reflect.typeMap_ = {};
|
||||
goog.debug.reflect.constructors_ = [];
|
||||
var implicitNs = goog.getObjectByName('goog.implicitNamespaces_') || {};
|
||||
|
||||
for (var ns in implicitNs) {
|
||||
if (implicitNs.hasOwnProperty(ns)) {
|
||||
var nsObj = goog.getObjectByName(ns);
|
||||
for (var name in nsObj) {
|
||||
if (nsObj.hasOwnProperty(name) && goog.isFunction(nsObj[name])) {
|
||||
goog.debug.reflect.registerType_(ns + '.' + name, nsObj[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goog.debug.reflect.registerType_('Array', Array);
|
||||
goog.debug.reflect.registerType_('Boolean', Boolean);
|
||||
goog.debug.reflect.registerType_('Date', Date);
|
||||
goog.debug.reflect.registerType_('Error', Error);
|
||||
goog.debug.reflect.registerType_('Function', Function);
|
||||
goog.debug.reflect.registerType_('Number', Number);
|
||||
goog.debug.reflect.registerType_('Object', Object);
|
||||
goog.debug.reflect.registerType_('String', String);
|
||||
|
||||
// The compiler gets upset if we alias regexp directly, because
|
||||
// then it can't optimize regexps as well. Just be sneaky about it,
|
||||
// because this is only for debugging.
|
||||
goog.debug.reflect.registerType_('RegExp', goog.global['RegExp']);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of a type of object.
|
||||
* @param {!Function} classConstructor A object constructor to get the name of.
|
||||
* @return {string|undefined} The string name of the class.
|
||||
*/
|
||||
goog.debug.reflect.className = function(classConstructor) {
|
||||
goog.debug.reflect.init_();
|
||||
if (goog.isDefAndNotNull(classConstructor)) {
|
||||
return goog.debug.reflect.typeMap_[goog.getUid(classConstructor)];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Guesses the real type of the object, even if its {@code toString} method is
|
||||
* overridden. Gives exact result for all goog.provided classes in non-compiled
|
||||
* code, and some often used native classes in compiled code too. Not tested in
|
||||
* multi-frame environment.
|
||||
*
|
||||
* Example use case to get better type information in the Watch tab of FireBug:
|
||||
* <pre>
|
||||
* Object.prototype.toString = function() {
|
||||
* return goog.debug.reflect.typeOf(this);
|
||||
* };
|
||||
* </pre>
|
||||
*
|
||||
* @param {*} obj An arbitrary variable to get the type of.
|
||||
* @return {string} The namespaced type of the argument or 'Object' if didn't
|
||||
* manage to determine it. Warning: in IE7 ActiveX (including DOM) objects
|
||||
* don't expose their type to JavaScript. Their {@code constructor}
|
||||
* property is undefined and they are not even the instances of the
|
||||
* {@code Object} type. This method will recognize them as 'ActiveXObject'.
|
||||
*/
|
||||
goog.debug.reflect.typeOf = function(obj) {
|
||||
// Check primitive types.
|
||||
if (!obj || goog.isNumber(obj) || goog.isString(obj) || goog.isBoolean(obj)) {
|
||||
return goog.typeOf(obj);
|
||||
}
|
||||
|
||||
// Check if the type is present in the registry.
|
||||
goog.debug.reflect.init_();
|
||||
if (obj.constructor) {
|
||||
// Some DOM objects such as document don't have constructor in IE7.
|
||||
var type = goog.debug.reflect.typeMap_[goog.getUid(obj.constructor)];
|
||||
if (type) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
// In IE8 the internal 'class' property of ActiveXObjects is Object, but
|
||||
// String(obj) tells their real type.
|
||||
var isActiveXObject = goog.global.ActiveXObject &&
|
||||
obj instanceof ActiveXObject;
|
||||
var typeString = isActiveXObject ? String(obj) :
|
||||
goog.debug.reflect.toString_.call(/** @type {Object} */ (obj));
|
||||
var match = typeString.match(/^\[object (\w+)\]$/);
|
||||
if (match) {
|
||||
var name = match[1];
|
||||
var ctor = goog.global[name];
|
||||
try {
|
||||
if (obj instanceof ctor) {
|
||||
return name;
|
||||
}
|
||||
} catch (e) {
|
||||
// instanceof may fail if the guessed name is not a real type.
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to Object or ActiveXObject.
|
||||
return isActiveXObject ? 'ActiveXObject' : 'Object';
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition the goog.debug.RelativeTimeProvider class.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.RelativeTimeProvider');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple object to keep track of a timestamp considered the start of
|
||||
* something. The main use is for the logger system to maintain a start time
|
||||
* that is occasionally reset. For example, in Gmail, we reset this relative
|
||||
* time at the start of a user action so that timings are offset from the
|
||||
* beginning of the action. This class also provides a singleton as the default
|
||||
* behavior for most use cases is to share the same start time.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider = function() {
|
||||
/**
|
||||
* The start time.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.relativeTimeStart_ = goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Default instance.
|
||||
* @type {goog.debug.RelativeTimeProvider}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.defaultInstance_ =
|
||||
new goog.debug.RelativeTimeProvider();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the start time to the specified time.
|
||||
* @param {number} timeStamp The start time.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.set = function(timeStamp) {
|
||||
this.relativeTimeStart_ = timeStamp;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the start time to now.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.reset = function() {
|
||||
this.set(goog.now());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The start time.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.prototype.get = function() {
|
||||
return this.relativeTimeStart_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.debug.RelativeTimeProvider} The default instance.
|
||||
*/
|
||||
goog.debug.RelativeTimeProvider.getDefaultInstance = function() {
|
||||
return goog.debug.RelativeTimeProvider.defaultInstance_;
|
||||
};
|
||||
@@ -0,0 +1,724 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Tracer class and associated classes.
|
||||
*
|
||||
* @see ../demos/tracer.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.debug.Trace');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.structs.Map');
|
||||
goog.require('goog.structs.SimplePool');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class used for singleton goog.debug.Trace. Used for timing slow points in
|
||||
* the code. Based on the java Tracer class but optimized for javascript.
|
||||
* See com.google.common.base.Tracer.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_ = function() {
|
||||
|
||||
/**
|
||||
* Events in order.
|
||||
* @type {Array.<goog.debug.Trace_.Event_>}
|
||||
* @private
|
||||
*/
|
||||
this.events_ = [];
|
||||
|
||||
/**
|
||||
* Outstanding events that have started but haven't yet ended. The keys are
|
||||
* numeric ids and the values are goog.debug.Trace_.Event_ objects.
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.outstandingEvents_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* Start time of the event trace
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.startTime_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to startTracer
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadStart_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to endTracer
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadEnd_ = 0;
|
||||
|
||||
/**
|
||||
* Cummulative overhead of calls to addComment
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerOverheadComment_ = 0;
|
||||
|
||||
/**
|
||||
* Keeps stats on different types of tracers. The keys are strings and the
|
||||
* values are goog.debug.Stat
|
||||
* @type {goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
this.stats_ = new goog.structs.Map();
|
||||
|
||||
/**
|
||||
* Total number of traces created in the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.tracerCount_ = 0;
|
||||
|
||||
/**
|
||||
* Total number of comments created in the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.commentCount_ = 0;
|
||||
|
||||
/**
|
||||
* Next id to use for the trace.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.nextId_ = 1;
|
||||
|
||||
/**
|
||||
* A pool for goog.debug.Trace_.Event_ objects so we don't keep creating and
|
||||
* garbage collecting these (which is very expensive in IE6).
|
||||
* @type {goog.structs.SimplePool}
|
||||
* @private
|
||||
*/
|
||||
this.eventPool_ = new goog.structs.SimplePool(0, 4000);
|
||||
this.eventPool_.createObject = function() {
|
||||
return new goog.debug.Trace_.Event_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A pool for goog.debug.Trace_.Stat_ objects so we don't keep creating and
|
||||
* garbage collecting these (which is very expensive in IE6).
|
||||
* @type {goog.structs.SimplePool}
|
||||
* @private
|
||||
*/
|
||||
this.statPool_ = new goog.structs.SimplePool(0, 50);
|
||||
this.statPool_.createObject = function() {
|
||||
return new goog.debug.Trace_.Stat_();
|
||||
};
|
||||
|
||||
var that = this;
|
||||
this.idPool_ = new goog.structs.SimplePool(0, 2000);
|
||||
|
||||
// TODO(nicksantos): SimplePool is supposed to only return objects.
|
||||
// Reconcile this so that we don't have to cast to number below.
|
||||
this.idPool_.createObject = function() {
|
||||
return String(that.nextId_++);
|
||||
};
|
||||
this.idPool_.disposeObject = function(obj) {};
|
||||
|
||||
/**
|
||||
* Default threshold below which a tracer shouldn't be reported
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.defaultThreshold_ = 3;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logger for the tracer
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.prototype.logger_ =
|
||||
goog.debug.Logger.getLogger('goog.debug.Trace');
|
||||
|
||||
|
||||
/**
|
||||
* Maximum size of the trace before we discard events
|
||||
* @type {number}
|
||||
*/
|
||||
goog.debug.Trace_.prototype.MAX_TRACE_SIZE = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Event type supported by tracer
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.debug.Trace_.EventType = {
|
||||
/**
|
||||
* Start event type
|
||||
*/
|
||||
START: 0,
|
||||
|
||||
/**
|
||||
* Stop event type
|
||||
*/
|
||||
STOP: 1,
|
||||
|
||||
/**
|
||||
* Comment event type
|
||||
*/
|
||||
COMMENT: 2
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class to keep track of a stat of a single tracer type. Stores the count
|
||||
* and cumulative time.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.Stat_ = function() {
|
||||
/**
|
||||
* Number of tracers
|
||||
* @type {number}
|
||||
*/
|
||||
this.count = 0;
|
||||
|
||||
/**
|
||||
* Cumulative time of traces
|
||||
* @type {number}
|
||||
*/
|
||||
this.time = 0;
|
||||
|
||||
/**
|
||||
* Total number of allocations for this tracer type
|
||||
* @type {number}
|
||||
*/
|
||||
this.varAlloc = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string|null|undefined}
|
||||
*/
|
||||
goog.debug.Trace_.Stat_.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} A string describing the tracer stat.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.Stat_.prototype.toString = function() {
|
||||
var sb = [];
|
||||
sb.push(this.type, ' ', this.count, ' (', Math.round(this.time * 10) / 10,
|
||||
' ms)');
|
||||
if (this.varAlloc) {
|
||||
sb.push(' [VarAlloc = ', this.varAlloc, ']');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Private class used to encapsulate a single event, either the start or stop
|
||||
* of a tracer.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.Event_ = function() {
|
||||
// the fields are different for different events - see usage in code
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @type {string|null|undefined}
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.type;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string for the event.
|
||||
* @param {number} startTime The start time of the trace to generate relative
|
||||
* times.
|
||||
* @param {number} prevTime The completion time of the previous event or -1.
|
||||
* @param {string} indent Extra indent for the message
|
||||
* if there was no previous event.
|
||||
* @return {string} The formatted tracer string.
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.toTraceString = function(startTime, prevTime,
|
||||
indent) {
|
||||
var sb = [];
|
||||
|
||||
if (prevTime == -1) {
|
||||
sb.push(' ');
|
||||
} else {
|
||||
sb.push(goog.debug.Trace_.longToPaddedString_(this.eventTime - prevTime));
|
||||
}
|
||||
|
||||
sb.push(' ', goog.debug.Trace_.formatTime_(this.eventTime - startTime));
|
||||
if (this.eventType == goog.debug.Trace_.EventType.START) {
|
||||
sb.push(' Start ');
|
||||
} else if (this.eventType == goog.debug.Trace_.EventType.STOP) {
|
||||
sb.push(' Done ');
|
||||
var delta = this.stopTime - this.startTime;
|
||||
sb.push(goog.debug.Trace_.longToPaddedString_(delta), ' ms ');
|
||||
} else {
|
||||
sb.push(' Comment ');
|
||||
}
|
||||
|
||||
sb.push(indent, this);
|
||||
if (this.totalVarAlloc > 0) {
|
||||
sb.push('[VarAlloc ', this.totalVarAlloc, '] ');
|
||||
}
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} A string describing the tracer event.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.Event_.prototype.toString = function() {
|
||||
if (this.type == null) {
|
||||
return this.comment;
|
||||
} else {
|
||||
return '[' + this.type + '] ' + this.comment;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add the ability to explicitly set the start time. This is useful for example
|
||||
* for measuring initial load time where you can set a variable as soon as the
|
||||
* main page of the app is loaded and then later call this function when the
|
||||
* Tracer code has been loaded.
|
||||
* @param {number} startTime The start time to set.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.setStartTime = function(startTime) {
|
||||
this.startTime_ = startTime;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes and resets the current trace
|
||||
* @param {number} defaultThreshold The default threshold below which the
|
||||
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.initCurrentTrace = function(defaultThreshold) {
|
||||
this.reset(defaultThreshold);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the current trace
|
||||
*/
|
||||
goog.debug.Trace_.prototype.clearCurrentTrace = function() {
|
||||
this.reset(0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resets the trace.
|
||||
* @param {number} defaultThreshold The default threshold below which the
|
||||
* tracer output will be supressed. Can be overridden on a per-Tracer basis.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.reset = function(defaultThreshold) {
|
||||
this.defaultThreshold_ = defaultThreshold;
|
||||
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var id = (/** @type {Object} */ this.eventPool_).id;
|
||||
if (id) {
|
||||
this.idPool_.releaseObject(id);
|
||||
}
|
||||
this.eventPool_.releaseObject(this.events_[i]);
|
||||
}
|
||||
|
||||
this.events_.length = 0;
|
||||
this.outstandingEvents_.clear();
|
||||
this.startTime_ = goog.debug.Trace_.now();
|
||||
this.tracerOverheadStart_ = 0;
|
||||
this.tracerOverheadEnd_ = 0;
|
||||
this.tracerOverheadComment_ = 0;
|
||||
this.tracerCount_ = 0;
|
||||
this.commentCount_ = 0;
|
||||
|
||||
var keys = this.stats_.getKeys();
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var stat = this.stats_.get(key);
|
||||
stat.count = 0;
|
||||
stat.time = 0;
|
||||
stat.varAlloc = 0;
|
||||
this.statPool_.releaseObject(/** @type {Object} */ (stat));
|
||||
}
|
||||
this.stats_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts a tracer
|
||||
* @param {string} comment A comment used to identify the tracer. Does not
|
||||
* need to be unique.
|
||||
* @param {string=} opt_type Type used to identify the tracer. If a Trace is
|
||||
* given a type (the first argument to the constructor) and multiple Traces
|
||||
* are done on that type then a "TOTAL line will be produced showing the
|
||||
* total number of traces and the sum of the time
|
||||
* ("TOTAL Database 2 (37 ms)" in our example). These traces should be
|
||||
* mutually exclusive or else the sum won't make sense (the time will
|
||||
* be double counted if the second starts before the first ends).
|
||||
* @return {number} The identifier for the tracer that should be passed to the
|
||||
* the stopTracer method.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.startTracer = function(comment, opt_type) {
|
||||
var tracerStartTime = goog.debug.Trace_.now();
|
||||
var varAlloc = this.getTotalVarAlloc();
|
||||
var outstandingEventCount = this.outstandingEvents_.getCount();
|
||||
if (this.events_.length + outstandingEventCount > this.MAX_TRACE_SIZE) {
|
||||
this.logger_.warning('Giant thread trace. Clearing to ' +
|
||||
'avoid memory leak.');
|
||||
// This is the more likely case. This usually means that we
|
||||
// either forgot to clear the trace or else we are performing a
|
||||
// very large number of events
|
||||
if (this.events_.length > this.MAX_TRACE_SIZE / 2) {
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var event = this.events_[i];
|
||||
if (event.id) {
|
||||
this.idPool_.releaseObject(event.id);
|
||||
}
|
||||
this.eventPool_.releaseObject(event);
|
||||
}
|
||||
this.events_.length = 0;
|
||||
}
|
||||
|
||||
// This is less likely and probably indicates that a lot of traces
|
||||
// aren't being closed. We want to avoid unnecessarily clearing
|
||||
// this though in case the events do eventually finish.
|
||||
if (outstandingEventCount > this.MAX_TRACE_SIZE / 2) {
|
||||
this.outstandingEvents_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
goog.debug.Logger.logToProfilers('Start : ' + comment);
|
||||
|
||||
var event = (/** @type {goog.debug.Trace_.Event_} */
|
||||
this.eventPool_.getObject());
|
||||
event.totalVarAlloc = varAlloc;
|
||||
event.eventType = goog.debug.Trace_.EventType.START;
|
||||
event.id = Number(this.idPool_.getObject());
|
||||
event.comment = comment;
|
||||
event.type = opt_type;
|
||||
this.events_.push(event);
|
||||
this.outstandingEvents_.set(String(event.id), event);
|
||||
this.tracerCount_++;
|
||||
var now = goog.debug.Trace_.now();
|
||||
event.startTime = event.eventTime = now;
|
||||
this.tracerOverheadStart_ += now - tracerStartTime;
|
||||
return event.id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stops a tracer
|
||||
* @param {number|undefined|null} id The id of the tracer that is ending.
|
||||
* @param {number=} opt_silenceThreshold Threshold below which the tracer is
|
||||
* silenced.
|
||||
* @return {?number} The elapsed time for the tracer or null if the tracer
|
||||
* identitifer was not recognized.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.stopTracer = function(id, opt_silenceThreshold) {
|
||||
// this used to call goog.isDef(opt_silenceThreshold) but that causes an
|
||||
// object allocation in IE for some reason (doh!). The following code doesn't
|
||||
// cause an allocation
|
||||
var now = goog.debug.Trace_.now();
|
||||
var silenceThreshold;
|
||||
if (opt_silenceThreshold === 0) {
|
||||
silenceThreshold = 0;
|
||||
} else if (opt_silenceThreshold) {
|
||||
silenceThreshold = opt_silenceThreshold;
|
||||
} else {
|
||||
silenceThreshold = this.defaultThreshold_;
|
||||
}
|
||||
|
||||
var startEvent = this.outstandingEvents_.get(String(id));
|
||||
if (startEvent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.outstandingEvents_.remove(String(id));
|
||||
|
||||
var stopEvent;
|
||||
var elapsed = now - startEvent.startTime;
|
||||
if (elapsed < silenceThreshold) {
|
||||
var count = this.events_.length;
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
var nextEvent = this.events_[i];
|
||||
if (nextEvent == startEvent) {
|
||||
this.events_.splice(i, 1);
|
||||
this.idPool_.releaseObject(startEvent.id);
|
||||
this.eventPool_.releaseObject(/** @type {Object} */ (startEvent));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
stopEvent = (/** @type {goog.debug.Trace_.Event_} */
|
||||
this.eventPool_.getObject());
|
||||
stopEvent.eventType = goog.debug.Trace_.EventType.STOP;
|
||||
stopEvent.startTime = startEvent.startTime;
|
||||
stopEvent.comment = startEvent.comment;
|
||||
stopEvent.type = startEvent.type;
|
||||
stopEvent.stopTime = stopEvent.eventTime = now;
|
||||
|
||||
this.events_.push(stopEvent);
|
||||
}
|
||||
|
||||
var type = startEvent.type;
|
||||
var stat = null;
|
||||
if (type) {
|
||||
stat = this.getStat_(type);
|
||||
stat.count++;
|
||||
stat.time += elapsed;
|
||||
}
|
||||
if (stopEvent) {
|
||||
goog.debug.Logger.logToProfilers('Stop : ' + stopEvent.comment);
|
||||
|
||||
stopEvent.totalVarAlloc = this.getTotalVarAlloc();
|
||||
|
||||
if (stat) {
|
||||
stat.varAlloc += (stopEvent.totalVarAlloc - startEvent.totalVarAlloc);
|
||||
}
|
||||
}
|
||||
var tracerFinishTime = goog.debug.Trace_.now();
|
||||
this.tracerOverheadEnd_ += tracerFinishTime - now;
|
||||
return elapsed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the ActiveX object that can be used to get GC tracing in IE6.
|
||||
* @param {Object} gcTracer GCTracer ActiveX object.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.setGcTracer = function(gcTracer) {
|
||||
this.gcTracer_ = gcTracer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of allocations since the GC stats were reset. Only
|
||||
* works in IE.
|
||||
* @return {number} The number of allocaitons or -1 if not supported.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getTotalVarAlloc = function() {
|
||||
var gcTracer = this.gcTracer_;
|
||||
// isTracing is defined on the ActiveX object.
|
||||
if (gcTracer && gcTracer['isTracing']()) {
|
||||
return gcTracer['totalVarAlloc'];
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a comment to the trace. Makes it possible to see when a specific event
|
||||
* happened in relation to the traces.
|
||||
* @param {string} comment A comment that is inserted into the trace.
|
||||
* @param {?string=} opt_type Type used to identify the tracer. If a comment is
|
||||
* given a type and multiple comments are done on that type then a "TOTAL
|
||||
* line will be produced showing the total number of comments of that type.
|
||||
* @param {?number=} opt_timeStamp The timestamp to insert the comment. If not
|
||||
* specified, the current time wil be used.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.addComment = function(comment, opt_type,
|
||||
opt_timeStamp) {
|
||||
var now = goog.debug.Trace_.now();
|
||||
var timeStamp = opt_timeStamp ? opt_timeStamp : now;
|
||||
|
||||
var eventComment = (/** @type {goog.debug.Trace_.Event_} */
|
||||
this.eventPool_.getObject());
|
||||
eventComment.eventType = goog.debug.Trace_.EventType.COMMENT;
|
||||
eventComment.eventTime = timeStamp;
|
||||
eventComment.type = opt_type;
|
||||
eventComment.comment = comment;
|
||||
eventComment.totalVarAlloc = this.getTotalVarAlloc();
|
||||
this.commentCount_++;
|
||||
|
||||
if (opt_timeStamp) {
|
||||
var numEvents = this.events_.length;
|
||||
for (var i = 0; i < numEvents; i++) {
|
||||
var event = this.events_[i];
|
||||
var eventTime = event.eventTime;
|
||||
|
||||
if (eventTime > timeStamp) {
|
||||
goog.array.insertAt(this.events_, eventComment, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == numEvents) {
|
||||
this.events_.push(eventComment);
|
||||
}
|
||||
} else {
|
||||
this.events_.push(eventComment);
|
||||
}
|
||||
|
||||
var type = eventComment.type;
|
||||
if (type) {
|
||||
var stat = this.getStat_(type);
|
||||
stat.count++;
|
||||
}
|
||||
|
||||
this.tracerOverheadComment_ += goog.debug.Trace_.now() - now;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a stat object for a particular type. The stat object is created if it
|
||||
* hasn't yet been.
|
||||
* @param {string} type The type of stat.
|
||||
* @return {goog.debug.Trace_.Stat_} The stat object.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getStat_ = function(type) {
|
||||
var stat = this.stats_.get(type);
|
||||
if (!stat) {
|
||||
stat = (/** @type {goog.debug.Trace_.Event_} */
|
||||
this.statPool_.getObject());
|
||||
stat.type = type;
|
||||
this.stats_.set(type, stat);
|
||||
}
|
||||
return /** @type {goog.debug.Trace_.Stat_} */(stat);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string for the current trace
|
||||
* @return {string} A formatted string that shows the timings of the current
|
||||
* trace.
|
||||
*/
|
||||
goog.debug.Trace_.prototype.getFormattedTrace = function() {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a formatted string that describes the thread trace.
|
||||
* @return {string} A formatted string.
|
||||
* @override
|
||||
*/
|
||||
goog.debug.Trace_.prototype.toString = function() {
|
||||
var sb = [];
|
||||
var etime = -1;
|
||||
var indent = [];
|
||||
for (var i = 0; i < this.events_.length; i++) {
|
||||
var e = this.events_[i];
|
||||
if (e.eventType == goog.debug.Trace_.EventType.STOP) {
|
||||
indent.pop();
|
||||
}
|
||||
sb.push(' ', e.toTraceString(this.startTime_, etime, indent.join('')));
|
||||
etime = e.eventTime;
|
||||
sb.push('\n');
|
||||
if (e.eventType == goog.debug.Trace_.EventType.START) {
|
||||
indent.push('| ');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.outstandingEvents_.getCount() != 0) {
|
||||
var now = goog.debug.Trace_.now();
|
||||
|
||||
sb.push(' Unstopped timers:\n');
|
||||
goog.iter.forEach(this.outstandingEvents_, function(startEvent) {
|
||||
sb.push(' ', startEvent, ' (', now - startEvent.startTime,
|
||||
' ms, started at ',
|
||||
goog.debug.Trace_.formatTime_(startEvent.startTime),
|
||||
')\n');
|
||||
});
|
||||
}
|
||||
|
||||
var statKeys = this.stats_.getKeys();
|
||||
for (var i = 0; i < statKeys.length; i++) {
|
||||
var stat = this.stats_.get(statKeys[i]);
|
||||
if (stat.count > 1) {
|
||||
sb.push(' TOTAL ', stat, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
sb.push('Total tracers created ', this.tracerCount_, '\n',
|
||||
'Total comments created ', this.commentCount_, '\n',
|
||||
'Overhead start: ', this.tracerOverheadStart_, ' ms\n',
|
||||
'Overhead end: ', this.tracerOverheadEnd_, ' ms\n',
|
||||
'Overhead comment: ', this.tracerOverheadComment_, ' ms\n');
|
||||
|
||||
return sb.join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts 'v' to a string and pads it with up to 3 spaces for
|
||||
* improved alignment. TODO there must be a better way
|
||||
* @param {number} v A number.
|
||||
* @return {string} A padded string.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.longToPaddedString_ = function(v) {
|
||||
v = Math.round(v);
|
||||
// todo (pupius) - there should be a generic string in goog.string for this
|
||||
var space = '';
|
||||
if (v < 1000) space = ' ';
|
||||
if (v < 100) space = ' ';
|
||||
if (v < 10) space = ' ';
|
||||
return space + v;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the sec.ms part of time (if time = "20:06:11.566", "11.566
|
||||
* @param {number} time The time in MS.
|
||||
* @return {string} A formatted string as sec.ms'.
|
||||
* @private
|
||||
*/
|
||||
goog.debug.Trace_.formatTime_ = function(time) {
|
||||
time = Math.round(time);
|
||||
var sec = (time / 1000) % 60;
|
||||
var ms = time % 1000;
|
||||
|
||||
// TODO their must be a nicer way to get zero padded integers
|
||||
return String(100 + sec).substring(1, 3) + '.' +
|
||||
String(1000 + ms).substring(1, 4);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current time. Done through a wrapper function so it can be
|
||||
* overridden by application code. Gmail has an ActiveX extension that provides
|
||||
* higher precision timing info.
|
||||
* @return {number} The current time in milliseconds.
|
||||
*/
|
||||
goog.debug.Trace_.now = function() {
|
||||
return goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Singleton trace object
|
||||
* @type {goog.debug.Trace_}
|
||||
*/
|
||||
goog.debug.Trace = new goog.debug.Trace_();
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
/** @nocompile */
|
||||
|
||||
['Big Table', 'Googlebot', 'Instant Indexing', 'Mustang', 'Page Rank',
|
||||
'Proto Buffer']
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
/** @nocompile */
|
||||
|
||||
[
|
||||
['apple',
|
||||
{name: 'Fuji', url: 'http://www.google.com/images?q=fuji+apple'},
|
||||
{name: 'Gala', url: 'http://www.google.com/images?q=gala+apple'},
|
||||
{name: 'Golden Delicious',
|
||||
url: 'http://www.google.com/images?q=golden delicious+apple'}
|
||||
],
|
||||
['citrus',
|
||||
{name: 'Lemon', url: 'http://www.google.com/images?q=lemon+fruit'},
|
||||
{name: 'Orange', url: 'http://www.google.com/images?q=orange+fruit'}
|
||||
],
|
||||
['berry',
|
||||
{name: 'Strawberry', url: 'http://www.google.com/images?q=strawberry+fruit'},
|
||||
{name: 'Blueberry', url: 'http://www.google.com/images?q=blueberry+fruit'},
|
||||
{name: 'Blackberry', url: 'http://www.google.com/images?q=blackberry+fruit'}
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.
|
||||
//
|
||||
// This file has been auto-generated by GenJsDeps, please do not edit.
|
||||
|
||||
goog.addDependency('demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'], ['goog.ui.equation.EquationEditorDialog']);
|
||||
goog.addDependency('demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']);
|
||||
goog.addDependency('demos/editor/helloworlddialog.js', ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent'], ['goog.dom.TagName', 'goog.events.Event', 'goog.string', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', 'goog.ui.editor.AbstractDialog.EventType']);
|
||||
goog.addDependency('demos/editor/helloworlddialogplugin.js', ['goog.demos.editor.HelloWorldDialogPlugin', 'goog.demos.editor.HelloWorldDialogPlugin.Command'], ['goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', 'goog.functions', 'goog.ui.editor.AbstractDialog.EventType']);
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
/**
|
||||
* @see equationeditor.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.demos.editor.EquationEditor');
|
||||
|
||||
goog.require('goog.ui.equation.EquationEditorDialog');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
goog.demos.editor.EquationEditor = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new editor and opens the dialog.
|
||||
* @param {string} initialEquation The initial equation value to use.
|
||||
*/
|
||||
goog.demos.editor.EquationEditor.prototype.openEditor = function(
|
||||
initialEquation) {
|
||||
var editorDialog = new goog.ui.equation.EquationEditorDialog(initialEquation);
|
||||
editorDialog.setVisible(true);
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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 A simple plugin that inserts 'Hello World!' on command. This
|
||||
* plugin is intended to be an example of a very simple plugin for plugin
|
||||
* developers.
|
||||
*
|
||||
* @author gak@google.com (Gregory Kick)
|
||||
* @see helloworld.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.demos.editor.HelloWorld');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.editor.Plugin');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugin to insert 'Hello World!' into an editable field.
|
||||
* @constructor
|
||||
* @extends {goog.editor.Plugin}
|
||||
*/
|
||||
goog.demos.editor.HelloWorld = function() {
|
||||
goog.editor.Plugin.call(this);
|
||||
};
|
||||
goog.inherits(goog.demos.editor.HelloWorld, goog.editor.Plugin);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.demos.editor.HelloWorld.prototype.getTrogClassId = function() {
|
||||
return 'HelloWorld';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Commands implemented by this plugin.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.demos.editor.HelloWorld.COMMAND = {
|
||||
HELLO_WORLD: '+helloWorld'
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.demos.editor.HelloWorld.prototype.isSupportedCommand = function(
|
||||
command) {
|
||||
return command == goog.demos.editor.HelloWorld.COMMAND.HELLO_WORLD;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Executes a command. Does not fire any BEFORECHANGE, CHANGE, or
|
||||
* SELECTIONCHANGE events (these are handled by the super class implementation
|
||||
* of {@code execCommand}.
|
||||
* @param {string} command Command to execute.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.demos.editor.HelloWorld.prototype.execCommandInternal = function(
|
||||
command) {
|
||||
var domHelper = this.getFieldObject().getEditableDomHelper();
|
||||
var range = this.getFieldObject().getRange();
|
||||
range.removeContents();
|
||||
var newNode =
|
||||
domHelper.createDom(goog.dom.TagName.SPAN, null, 'Hello World!');
|
||||
range.insertNode(newNode, false);
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 An example of how to write a dialog to be opened by a plugin.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.demos.editor.HelloWorldDialog');
|
||||
goog.provide('goog.demos.editor.HelloWorldDialog.OkEvent');
|
||||
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.ui.editor.AbstractDialog');
|
||||
goog.require('goog.ui.editor.AbstractDialog.Builder');
|
||||
goog.require('goog.ui.editor.AbstractDialog.EventType');
|
||||
|
||||
|
||||
// *** Public interface ***************************************************** //
|
||||
|
||||
/**
|
||||
* Creates a dialog to let the user enter a customized hello world message.
|
||||
* @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
|
||||
* dialog's dom structure.
|
||||
* @constructor
|
||||
* @extends {goog.ui.editor.AbstractDialog}
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog = function(domHelper) {
|
||||
goog.ui.editor.AbstractDialog.call(this, domHelper);
|
||||
};
|
||||
goog.inherits(goog.demos.editor.HelloWorldDialog,
|
||||
goog.ui.editor.AbstractDialog);
|
||||
|
||||
|
||||
// *** Event **************************************************************** //
|
||||
|
||||
/**
|
||||
* OK event object for the hello world dialog.
|
||||
* @param {string} message Customized hello world message chosen by the user.
|
||||
* @constructor
|
||||
* @extends {goog.events.Event}
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.OkEvent = function(message) {
|
||||
this.message = message;
|
||||
};
|
||||
goog.inherits(goog.demos.editor.HelloWorldDialog.OkEvent, goog.events.Event);
|
||||
|
||||
/**
|
||||
* Event type.
|
||||
* @type {goog.ui.editor.AbstractDialog.EventType}
|
||||
* @override
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.OkEvent.prototype.type =
|
||||
goog.ui.editor.AbstractDialog.EventType.OK;
|
||||
|
||||
/**
|
||||
* Customized hello world message chosen by the user.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.OkEvent.prototype.message;
|
||||
|
||||
|
||||
// *** Protected interface ************************************************** //
|
||||
|
||||
/** @override */
|
||||
goog.demos.editor.HelloWorldDialog.prototype.createDialogControl = function() {
|
||||
var builder = new goog.ui.editor.AbstractDialog.Builder(this);
|
||||
/** @desc Title of the hello world dialog. */
|
||||
var MSG_HELLO_WORLD_DIALOG_TITLE = goog.getMsg('Add a Hello World message');
|
||||
builder.setTitle(MSG_HELLO_WORLD_DIALOG_TITLE).
|
||||
setContent(this.createContent_());
|
||||
return builder.build();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns the event object to be used when dispatching the OK
|
||||
* event to listeners, or returns null to prevent the dialog from closing.
|
||||
* @param {goog.events.Event} e The event object dispatched by the wrapped
|
||||
* dialog.
|
||||
* @return {goog.demos.editor.HelloWorldDialog.OkEvent} The event object to be
|
||||
* used when dispatching the OK event to listeners.
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.prototype.createOkEvent = function(e) {
|
||||
var message = this.getMessage_();
|
||||
if (message &&
|
||||
goog.demos.editor.HelloWorldDialog.isValidHelloWorld_(message)) {
|
||||
return new goog.demos.editor.HelloWorldDialog.OkEvent(message);
|
||||
} else {
|
||||
/** @desc Error message telling the user why their message was rejected. */
|
||||
var MSG_HELLO_WORLD_DIALOG_ERROR =
|
||||
goog.getMsg('Your message must contain the words "hello" and "world".');
|
||||
this.dom.getWindow().alert(MSG_HELLO_WORLD_DIALOG_ERROR);
|
||||
return null; // Prevents the dialog from closing.
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// *** Private implementation *********************************************** //
|
||||
|
||||
/**
|
||||
* Input element where the user will type their hello world message.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.prototype.input_;
|
||||
|
||||
|
||||
/**
|
||||
* Creates the DOM structure that makes up the dialog's content area.
|
||||
* @return {Element} The DOM structure that makes up the dialog's content area.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.prototype.createContent_ = function() {
|
||||
/** @desc Sample hello world message to prepopulate the dialog with. */
|
||||
var MSG_HELLO_WORLD_DIALOG_SAMPLE = goog.getMsg('Hello, world!');
|
||||
this.input_ = this.dom.createDom(goog.dom.TagName.INPUT,
|
||||
{size: 25, value: MSG_HELLO_WORLD_DIALOG_SAMPLE});
|
||||
/** @desc Prompt telling the user to enter a hello world message. */
|
||||
var MSG_HELLO_WORLD_DIALOG_PROMPT =
|
||||
goog.getMsg('Enter your Hello World message');
|
||||
return this.dom.createDom(goog.dom.TagName.DIV,
|
||||
null,
|
||||
[MSG_HELLO_WORLD_DIALOG_PROMPT, this.input_]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the hello world message currently typed into the dialog's input.
|
||||
* @return {?string} The hello world message currently typed into the dialog's
|
||||
* input, or null if called before the input is created.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.prototype.getMessage_ = function() {
|
||||
return this.input_ && this.input_.value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the given message contains the strings "hello" and
|
||||
* "world". Case-insensitive and order doesn't matter.
|
||||
* @param {string} message The message to be checked.
|
||||
* @return {boolean} Whether or not the given message contains the strings
|
||||
* "hello" and "world".
|
||||
* @private
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialog.isValidHelloWorld_ = function(message) {
|
||||
message = message.toLowerCase();
|
||||
return goog.string.contains(message, 'hello') &&
|
||||
goog.string.contains(message, 'world');
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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 An example of how to write a dialog plugin.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.demos.editor.HelloWorldDialogPlugin');
|
||||
goog.provide('goog.demos.editor.HelloWorldDialogPlugin.Command');
|
||||
|
||||
goog.require('goog.demos.editor.HelloWorldDialog');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.editor.plugins.AbstractDialogPlugin');
|
||||
goog.require('goog.editor.range');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.ui.editor.AbstractDialog.EventType');
|
||||
|
||||
|
||||
// *** Public interface ***************************************************** //
|
||||
|
||||
/**
|
||||
* A plugin that opens the hello world dialog.
|
||||
* @constructor
|
||||
* @extends {goog.editor.plugins.AbstractDialogPlugin}
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialogPlugin = function() {
|
||||
goog.editor.plugins.AbstractDialogPlugin.call(this,
|
||||
goog.demos.editor.HelloWorldDialogPlugin.Command.HELLO_WORLD_DIALOG);
|
||||
};
|
||||
goog.inherits(goog.demos.editor.HelloWorldDialogPlugin,
|
||||
goog.editor.plugins.AbstractDialogPlugin);
|
||||
|
||||
/**
|
||||
* Commands implemented by this plugin.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialogPlugin.Command = {
|
||||
HELLO_WORLD_DIALOG: 'helloWorldDialog'
|
||||
};
|
||||
|
||||
/** @override */
|
||||
goog.demos.editor.HelloWorldDialogPlugin.prototype.getTrogClassId =
|
||||
goog.functions.constant('HelloWorldDialog');
|
||||
|
||||
|
||||
// *** Protected interface ************************************************** //
|
||||
|
||||
/**
|
||||
* Creates a new instance of the dialog and registers for the relevant events.
|
||||
* @param {goog.dom.DomHelper} dialogDomHelper The dom helper to be used to
|
||||
* create the dialog.
|
||||
* @return {goog.demos.editor.HelloWorldDialog} The dialog.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialogPlugin.prototype.createDialog = function(
|
||||
dialogDomHelper) {
|
||||
var dialog = new goog.demos.editor.HelloWorldDialog(dialogDomHelper);
|
||||
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.OK,
|
||||
this.handleOk_,
|
||||
false,
|
||||
this);
|
||||
return dialog;
|
||||
};
|
||||
|
||||
|
||||
// *** Private implementation *********************************************** //
|
||||
|
||||
/**
|
||||
* Handles the OK event from the dialog by inserting the hello world message
|
||||
* into the field.
|
||||
* @param {goog.demos.editor.HelloWorldDialog.OkEvent} e OK event object.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.editor.HelloWorldDialogPlugin.prototype.handleOk_ = function(e) {
|
||||
// First restore the selection so we can manipulate the field's content
|
||||
// according to what was selected.
|
||||
this.restoreOriginalSelection();
|
||||
|
||||
// Notify listeners that the field's contents are about to change.
|
||||
this.getFieldObject().dispatchBeforeChange();
|
||||
|
||||
// Now we can clear out what was previously selected (if anything).
|
||||
var range = this.getFieldObject().getRange();
|
||||
range.removeContents();
|
||||
// And replace it with a span containing our hello world message.
|
||||
var createdNode = this.getFieldDomHelper().createDom(goog.dom.TagName.SPAN,
|
||||
null,
|
||||
e.message);
|
||||
createdNode = range.insertNode(createdNode, false);
|
||||
// Place the cursor at the end of the new text node (false == to the right).
|
||||
goog.editor.range.placeCursorNextTo(createdNode, false);
|
||||
|
||||
// Notify listeners that the field's selection has changed.
|
||||
this.getFieldObject().dispatchSelectionChangeEvent();
|
||||
// Notify listeners that the field's contents have changed.
|
||||
this.getFieldObject().dispatchChange();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
// 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 simple, sample component.
|
||||
*
|
||||
*/
|
||||
goog.provide('goog.demos.SampleComponent');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classes');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.events.KeyHandler');
|
||||
goog.require('goog.events.KeyHandler.EventType');
|
||||
goog.require('goog.ui.Component');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple box that changes colour when clicked. This class demonstrates the
|
||||
* goog.ui.Component API, and is keyboard accessible, as per
|
||||
* http://wiki/Main/ClosureKeyboardAccessible
|
||||
*
|
||||
* @param {string=} opt_label A label to display. Defaults to "Click Me" if none
|
||||
* provided.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper to use.
|
||||
*
|
||||
* @extends {goog.ui.Component}
|
||||
* @constructor
|
||||
*/
|
||||
goog.demos.SampleComponent = function(opt_label, opt_domHelper) {
|
||||
goog.ui.Component.call(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The label to display.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.initialLabel_ = opt_label || 'Click Me';
|
||||
|
||||
/**
|
||||
* The current color.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.color_ = 'red';
|
||||
|
||||
/**
|
||||
* Event handler for this object.
|
||||
* @type {goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eh_ = new goog.events.EventHandler(this);
|
||||
|
||||
/**
|
||||
* Keyboard handler for this object. This object is created once the
|
||||
* component's DOM element is known.
|
||||
*
|
||||
* @type {goog.events.KeyHandler?}
|
||||
* @private
|
||||
*/
|
||||
this.kh_ = null;
|
||||
};
|
||||
goog.inherits(goog.demos.SampleComponent, goog.ui.Component);
|
||||
|
||||
|
||||
/**
|
||||
* Changes the color of the element.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.changeColor_ = function() {
|
||||
if (this.color_ == 'red') {
|
||||
this.color_ = 'green';
|
||||
} else if (this.color_ == 'green') {
|
||||
this.color_ = 'blue';
|
||||
} else {
|
||||
this.color_ = 'red';
|
||||
}
|
||||
this.getElement().style.backgroundColor = this.color_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an initial DOM representation for the component.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.createDom = function() {
|
||||
this.decorateInternal(this.dom_.createElement('div'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decorates an existing HTML DIV element as a SampleComponent.
|
||||
*
|
||||
* @param {Element} element The DIV element to decorate. The element's
|
||||
* text, if any will be used as the component's label.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.decorateInternal = function(element) {
|
||||
goog.demos.SampleComponent.superClass_.decorateInternal.call(this, element);
|
||||
if (!this.getLabelText()) {
|
||||
this.setLabelText(this.initialLabel_);
|
||||
}
|
||||
|
||||
var elem = this.getElement();
|
||||
goog.dom.classes.add(elem, goog.getCssName('goog-sample-component'));
|
||||
elem.style.backgroundColor = this.color_;
|
||||
elem.tabIndex = 0;
|
||||
|
||||
this.kh_ = new goog.events.KeyHandler(elem);
|
||||
this.eh_.listen(this.kh_, goog.events.KeyHandler.EventType.KEY, this.onKey_);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.demos.SampleComponent.prototype.disposeInternal = function() {
|
||||
goog.demos.SampleComponent.superClass_.disposeInternal.call(this);
|
||||
this.eh_.dispose();
|
||||
if (this.kh_) {
|
||||
this.kh_.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when component's element is known to be in the document.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.enterDocument = function() {
|
||||
goog.demos.SampleComponent.superClass_.enterDocument.call(this);
|
||||
this.eh_.listen(this.getElement(), goog.events.EventType.CLICK,
|
||||
this.onDivClicked_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when component's element is known to have been removed from the
|
||||
* document.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.exitDocument = function() {
|
||||
goog.demos.SampleComponent.superClass_.exitDocument.call(this);
|
||||
this.eh_.unlisten(this.getElement(), goog.events.EventType.CLICK,
|
||||
this.onDivClicked_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current label text.
|
||||
*
|
||||
* @return {string} The current text set into the label, or empty string if
|
||||
* none set.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.getLabelText = function() {
|
||||
if (!this.getElement()) {
|
||||
return '';
|
||||
}
|
||||
return goog.dom.getTextContent(this.getElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles DIV element clicks, causing the DIV's colour to change.
|
||||
* @param {goog.events.Event} event The click event.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.onDivClicked_ = function(event) {
|
||||
this.changeColor_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Fired when user presses a key while the DIV has focus. If the user presses
|
||||
* space or enter, the color will be changed.
|
||||
* @param {goog.events.Event} event The key event.
|
||||
* @private
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.onKey_ = function(event) {
|
||||
var keyCodes = goog.events.KeyCodes;
|
||||
if (event.keyCode == keyCodes.SPACE || event.keyCode == keyCodes.ENTER) {
|
||||
this.changeColor_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the current label text. Has no effect if component is not rendered.
|
||||
*
|
||||
* @param {string} text The text to set as the label.
|
||||
*/
|
||||
goog.demos.SampleComponent.prototype.setLabelText = function(text) {
|
||||
if (this.getElement()) {
|
||||
goog.dom.setTextContent(this.getElement(), text);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,260 @@
|
||||
// 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.
|
||||
var testData =
|
||||
['Countries', [['A', [['Afghanistan'],
|
||||
['Albania'],
|
||||
['Algeria'],
|
||||
['American Samoa'],
|
||||
['Andorra'],
|
||||
['Angola'],
|
||||
['Anguilla'],
|
||||
['Antarctica'],
|
||||
['Antigua and Barbuda'],
|
||||
['Argentina'],
|
||||
['Armenia'],
|
||||
['Aruba'],
|
||||
['Australia'],
|
||||
['Austria'],
|
||||
['Azerbaijan']]],
|
||||
['B', [['Bahamas'],
|
||||
['Bahrain'],
|
||||
['Bangladesh'],
|
||||
['Barbados'],
|
||||
['Belarus'],
|
||||
['Belgium'],
|
||||
['Belize'],
|
||||
['Benin'],
|
||||
['Bermuda'],
|
||||
['Bhutan'],
|
||||
['Bolivia'],
|
||||
['Bosnia and Herzegovina'],
|
||||
['Botswana'],
|
||||
['Bouvet Island'],
|
||||
['Brazil'],
|
||||
['British Indian Ocean Territory'],
|
||||
['Brunei Darussalam'],
|
||||
['Bulgaria'],
|
||||
['Burkina Faso'],
|
||||
['Burundi']]],
|
||||
['C', [['Cambodia'],
|
||||
['Cameroon'],
|
||||
['Canada'],
|
||||
['Cape Verde'],
|
||||
['Cayman Islands'],
|
||||
['Central African Republic'],
|
||||
['Chad'],
|
||||
['Chile'],
|
||||
['China'],
|
||||
['Christmas Island'],
|
||||
['Cocos (Keeling) Islands'],
|
||||
['Colombia'],
|
||||
['Comoros'],
|
||||
['Congo'],
|
||||
['Congo, the Democratic Republic of the'],
|
||||
['Cook Islands'],
|
||||
['Costa Rica'],
|
||||
['Croatia'],
|
||||
['Cuba'],
|
||||
['Cyprus'],
|
||||
['Czech Republic'],
|
||||
['C\u00f4te d\u2019Ivoire']]],
|
||||
['D', [['Denmark'],
|
||||
['Djibouti'],
|
||||
['Dominica'],
|
||||
['Dominican Republic']]],
|
||||
['E', [['Ecuador'],
|
||||
['Egypt'],
|
||||
['El Salvador'],
|
||||
['Equatorial Guinea'],
|
||||
['Eritrea'],
|
||||
['Estonia'],
|
||||
['Ethiopia']]],
|
||||
['F', [['Falkland Islands (Malvinas)'],
|
||||
['Faroe Islands'],
|
||||
['Fiji'],
|
||||
['Finland'],
|
||||
['France'],
|
||||
['French Guiana'],
|
||||
['French Polynesia'],
|
||||
['French Southern Territories']]],
|
||||
['G', [['Gabon'],
|
||||
['Gambia'],
|
||||
['Georgia'],
|
||||
['Germany'],
|
||||
['Ghana'],
|
||||
['Gibraltar'],
|
||||
['Greece'],
|
||||
['Greenland'],
|
||||
['Grenada'],
|
||||
['Guadeloupe'],
|
||||
['Guam'],
|
||||
['Guatemala'],
|
||||
['Guernsey'],
|
||||
['Guinea'],
|
||||
['Guinea-Bissau'],
|
||||
['Guyana']]],
|
||||
['H', [['Haiti'],
|
||||
['Heard Island and McDonald Islands'],
|
||||
['Holy See (Vatican City State)'],
|
||||
['Honduras'],
|
||||
['Hong Kong'],
|
||||
['Hungary']]],
|
||||
['I', [['Iceland'],
|
||||
['India'],
|
||||
['Indonesia'],
|
||||
['Iran, Islamic Republic of'],
|
||||
['Iraq'],
|
||||
['Ireland'],
|
||||
['Isle of Man'],
|
||||
['Israel'],
|
||||
['Italy']]],
|
||||
['J', [['Jamaica'],
|
||||
['Japan'],
|
||||
['Jersey'],
|
||||
['Jordan']]],
|
||||
['K', [['Kazakhstan'],
|
||||
['Kenya'],
|
||||
['Kiribati'],
|
||||
['Korea, Democratic People\u2019s Republic of'],
|
||||
['Korea, Republic of'],
|
||||
['Kuwait'],
|
||||
['Kyrgyzstan']]],
|
||||
['L', [['Lao People\u2019s Democratic Republic'],
|
||||
['Latvia'],
|
||||
['Lebanon'],
|
||||
['Lesotho'],
|
||||
['Liberia'],
|
||||
['Libyan Arab Jamahiriya'],
|
||||
['Liechtenstein'],
|
||||
['Lithuania'],
|
||||
['Luxembourg']]],
|
||||
['M', [['Macao'],
|
||||
['Macedonia, the former Yugoslav Republic of'],
|
||||
['Madagascar'],
|
||||
['Malawi'],
|
||||
['Malaysia'],
|
||||
['Maldives'],
|
||||
['Mali'],
|
||||
['Malta'],
|
||||
['Marshall Islands'],
|
||||
['Martinique'],
|
||||
['Mauritania'],
|
||||
['Mauritius'],
|
||||
['Mayotte'],
|
||||
['Mexico'],
|
||||
['Micronesia, Federated States of'],
|
||||
['Moldova, Republic of'],
|
||||
['Monaco'],
|
||||
['Mongolia'],
|
||||
['Montenegro'],
|
||||
['Montserrat'],
|
||||
['Morocco'],
|
||||
['Mozambique'],
|
||||
['Myanmar']]],
|
||||
['N', [['Namibia'],
|
||||
['Nauru'],
|
||||
['Nepal'],
|
||||
['Netherlands'],
|
||||
['Netherlands Antilles'],
|
||||
['New Caledonia'],
|
||||
['New Zealand'],
|
||||
['Nicaragua'],
|
||||
['Niger'],
|
||||
['Nigeria'],
|
||||
['Niue'],
|
||||
['Norfolk Island'],
|
||||
['Northern Mariana Islands'],
|
||||
['Norway']]],
|
||||
['O', [['Oman']]],
|
||||
['P', [['Pakistan'],
|
||||
['Palau'],
|
||||
['Palestinian Territory, Occupied'],
|
||||
['Panama'],
|
||||
['Papua New Guinea'],
|
||||
['Paraguay'],
|
||||
['Peru'],
|
||||
['Philippines'],
|
||||
['Pitcairn'],
|
||||
['Poland'],
|
||||
['Portugal'],
|
||||
['Puerto Rico']]],
|
||||
['Q', [['Qatar']]],
|
||||
['R', [['Romania'],
|
||||
['Russian Federation'],
|
||||
['Rwanda'],
|
||||
['R\u00e9union']]],
|
||||
['S', [['Saint Barth\u00e9lemy'],
|
||||
['Saint Helena'],
|
||||
['Saint Kitts and Nevis'],
|
||||
['Saint Lucia'],
|
||||
['Saint Martin (French part)'],
|
||||
['Saint Pierre and Miquelon'],
|
||||
['Saint Vincent and the Grenadines'],
|
||||
['Samoa'],
|
||||
['San Marino'],
|
||||
['Sao Tome and Principe'],
|
||||
['Saudi Arabia'],
|
||||
['Senegal'],
|
||||
['Serbia'],
|
||||
['Seychelles'],
|
||||
['Sierra Leone'],
|
||||
['Singapore'],
|
||||
['Slovakia'],
|
||||
['Slovenia'],
|
||||
['Solomon Islands'],
|
||||
['Somalia'],
|
||||
['South Africa'],
|
||||
['South Georgia and the South Sandwich Islands'],
|
||||
['Spain'],
|
||||
['Sri Lanka'],
|
||||
['Sudan'],
|
||||
['Suriname'],
|
||||
['Svalbard and Jan Mayen'],
|
||||
['Swaziland'],
|
||||
['Sweden'],
|
||||
['Switzerland'],
|
||||
['Syrian Arab Republic']]],
|
||||
['T', [['Taiwan, Province of China'],
|
||||
['Tajikistan'],
|
||||
['Tanzania, United Republic of'],
|
||||
['Thailand'],
|
||||
['Timor-Leste'],
|
||||
['Togo'],
|
||||
['Tokelau'],
|
||||
['Tonga'],
|
||||
['Trinidad and Tobago'],
|
||||
['Tunisia'],
|
||||
['Turkey'],
|
||||
['Turkmenistan'],
|
||||
['Turks and Caicos Islands'],
|
||||
['Tuvalu']]],
|
||||
['U', [['Uganda'],
|
||||
['Ukraine'],
|
||||
['United Arab Emirates'],
|
||||
['United Kingdom'],
|
||||
['United States'],
|
||||
['United States Minor Outlying Islands'],
|
||||
['Uruguay'],
|
||||
['Uzbekistan']]],
|
||||
['V', [['Vanuatu'],
|
||||
['Venezuela'],
|
||||
['Viet Nam'],
|
||||
['Virgin Islands, British'],
|
||||
['Virgin Islands, U.S.']]],
|
||||
['W', [['Wallis and Futuna'],
|
||||
['Western Sahara']]],
|
||||
['Y', [['Yemen']]],
|
||||
['Z', [['Zambia'],
|
||||
['Zimbabwe']]],
|
||||
['\u00c5', [['\u00c5land Islands']]]]]
|
||||
@@ -0,0 +1,304 @@
|
||||
// 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 Contains application code for the XPC demo.
|
||||
* This script is used in both the container page and the iframe.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.net.xpc.CrossPageChannel');
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for the demo. We don't use goog.provide here because it's not a
|
||||
* real module (cannot be required).
|
||||
*/
|
||||
var xpcdemo = {};
|
||||
|
||||
|
||||
/**
|
||||
* Global function to kick off initialization in the containing document.
|
||||
*/
|
||||
goog.global.initOuter = function() {
|
||||
goog.events.listen(window, 'load', function() { xpcdemo.initOuter(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Global function to kick off initialization in the iframe.
|
||||
*/
|
||||
goog.global.initInner = function() {
|
||||
goog.events.listen(window, 'load', function() { xpcdemo.initInner(); });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes XPC in the containing page.
|
||||
*/
|
||||
xpcdemo.initOuter = function() {
|
||||
// Build the configuration object.
|
||||
var cfg = {};
|
||||
|
||||
var ownUri = new goog.Uri(window.location.href);
|
||||
var relayUri = ownUri.resolve(new goog.Uri('relay.html'));
|
||||
var pollUri = ownUri.resolve(new goog.Uri('blank.html'));
|
||||
|
||||
// Determine the peer domain. Uses the value of the URI-parameter
|
||||
// 'peerdomain'. If that parameter is not present, it falls back to
|
||||
// the own domain so that the demo will work out of the box (but
|
||||
// communication will of course not cross domain-boundaries). For
|
||||
// real cross-domain communication, the easiest way is to point two
|
||||
// different host-names to the same webserver and then hit the
|
||||
// following URI:
|
||||
// http://host1.com/path/to/closure/demos/xpc/index.html?peerdomain=host2.com
|
||||
var peerDomain = ownUri.getParameterValue('peerdomain') || ownUri.getDomain();
|
||||
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_RELAY_URI] = relayUri.toString();
|
||||
cfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =
|
||||
relayUri.setDomain(peerDomain).toString();
|
||||
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] = pollUri.toString();
|
||||
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
|
||||
pollUri.setDomain(peerDomain).toString();
|
||||
|
||||
|
||||
// Force transport to be used if tp-parameter is set.
|
||||
var tp = ownUri.getParameterValue('tp');
|
||||
if (tp) {
|
||||
cfg[goog.net.xpc.CfgFields.TRANSPORT] = parseInt(tp, 10);
|
||||
}
|
||||
|
||||
|
||||
// Construct the URI of the peer page.
|
||||
|
||||
var peerUri = ownUri.resolve(
|
||||
new goog.Uri('inner.html')).setDomain(peerDomain);
|
||||
// Passthrough of verbose and compiled flags.
|
||||
if (goog.isDef(ownUri.getParameterValue('verbose'))) {
|
||||
peerUri.setParameterValue('verbose', '');
|
||||
}
|
||||
if (goog.isDef(ownUri.getParameterValue('compiled'))) {
|
||||
peerUri.setParameterValue('compiled', '');
|
||||
}
|
||||
|
||||
cfg[goog.net.xpc.CfgFields.PEER_URI] = peerUri;
|
||||
|
||||
// Instantiate the channel.
|
||||
xpcdemo.channel = new goog.net.xpc.CrossPageChannel(cfg);
|
||||
|
||||
// Create the peer iframe.
|
||||
xpcdemo.peerIframe = xpcdemo.channel.createPeerIframe(
|
||||
goog.dom.getElement('iframeContainer'));
|
||||
|
||||
xpcdemo.initCommon_();
|
||||
|
||||
goog.dom.getElement('inactive').style.display = 'none';
|
||||
goog.dom.getElement('active').style.display = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialization in the iframe.
|
||||
*/
|
||||
xpcdemo.initInner = function() {
|
||||
// Get the channel configuration passed by the containing document.
|
||||
var cfg = goog.json.parse(
|
||||
(new goog.Uri(window.location.href)).getParameterValue('xpc'));
|
||||
|
||||
xpcdemo.channel = new goog.net.xpc.CrossPageChannel(cfg);
|
||||
|
||||
xpcdemo.initCommon_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the demo.
|
||||
* Registers service-handlers and connects the channel.
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.initCommon_ = function() {
|
||||
var xpcLogger = goog.debug.Logger.getLogger('goog.net.xpc');
|
||||
xpcLogger.addHandler(function(logRecord) {
|
||||
xpcdemo.log('[XPC] ' + logRecord.getMessage());
|
||||
});
|
||||
xpcLogger.setLevel(window.location.href.match(/verbose/) ?
|
||||
goog.debug.Logger.Level.ALL : goog.debug.Logger.Level.INFO);
|
||||
|
||||
// Register services.
|
||||
xpcdemo.channel.registerService('log', xpcdemo.log);
|
||||
xpcdemo.channel.registerService('ping', xpcdemo.pingHandler_);
|
||||
xpcdemo.channel.registerService('events', xpcdemo.eventsMsgHandler_);
|
||||
|
||||
// Connect the channel.
|
||||
xpcdemo.channel.connect(function() {
|
||||
xpcdemo.channel.send('log', 'Hi from ' + window.location.host);
|
||||
goog.events.listen(goog.dom.getElement('clickfwd'),
|
||||
'click', xpcdemo.mouseEventHandler_);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Kills the peer iframe and the disposes the channel.
|
||||
*/
|
||||
xpcdemo.teardown = function() {
|
||||
goog.events.unlisten(goog.dom.getElement('clickfwd'),
|
||||
goog.events.EventType.CLICK, xpcdemo.mouseEventHandler_);
|
||||
|
||||
xpcdemo.channel.dispose();
|
||||
delete xpcdemo.channel;
|
||||
|
||||
goog.dom.removeNode(xpcdemo.peerIframe);
|
||||
xpcdemo.peerIframe = null;
|
||||
|
||||
goog.dom.getElement('inactive').style.display = '';
|
||||
goog.dom.getElement('active').style.display = 'none';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Logging function. Inserts log-message into element with it id 'console'.
|
||||
* @param {string} msgString The log-message.
|
||||
*/
|
||||
xpcdemo.log = function(msgString) {
|
||||
xpcdemo.consoleElm || (xpcdemo.consoleElm = goog.dom.getElement('console'));
|
||||
var msgElm = goog.dom.createDom('div');
|
||||
msgElm.innerHTML = msgString;
|
||||
xpcdemo.consoleElm.insertBefore(msgElm, xpcdemo.consoleElm.firstChild);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a ping request to the peer.
|
||||
*/
|
||||
xpcdemo.ping = function() {
|
||||
// send current time
|
||||
xpcdemo.channel.send('ping', goog.now() + '');
|
||||
};
|
||||
|
||||
/**
|
||||
* The handler function for incoming pings (messages sent to the service
|
||||
* called 'ping');
|
||||
* @param {string} payload The message payload.
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.pingHandler_ = function(payload) {
|
||||
// is the incoming message a response to a ping we sent?
|
||||
if (payload.charAt(0) == '#') {
|
||||
// calculate roundtrip time and log
|
||||
var dt = goog.now() - parseInt(payload.substring(1), 10);
|
||||
xpcdemo.log('roundtrip: ' + dt + 'ms');
|
||||
} else {
|
||||
// incoming message is a ping initiated from peer
|
||||
// -> prepend with '#' and send back
|
||||
xpcdemo.channel.send('ping', '#' + payload);
|
||||
xpcdemo.log('ping reply sent');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Counter for mousemove events.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.mmCount_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Holds timestamp when the last mousemove rate has been logged.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.mmLastRateOutput_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Start mousemove event forwarding. Registers a listener on the document which
|
||||
* sends them over the channel.
|
||||
*/
|
||||
xpcdemo.startMousemoveForwarding = function() {
|
||||
goog.events.listen(document, goog.events.EventType.MOUSEMOVE,
|
||||
xpcdemo.mouseEventHandler_);
|
||||
xpcdemo.mmLastRateOutput_ = goog.now();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stop mousemove event forwarding.
|
||||
*/
|
||||
xpcdemo.stopMousemoveForwarding = function() {
|
||||
goog.events.unlisten(document, goog.events.EventType.MOUSEMOVE,
|
||||
xpcdemo.mouseEventHandler_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function to be used as handler for mouse-events.
|
||||
* @param {goog.events.BrowserEvent} e The mouse event.
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.mouseEventHandler_ = function(e) {
|
||||
xpcdemo.channel.send('events',
|
||||
[e.type, e.clientX, e.clientY, goog.now()].join(','));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handler for the 'events' service.
|
||||
* @param {string} payload The string returned from the xpcdemo.
|
||||
* @private
|
||||
*/
|
||||
xpcdemo.eventsMsgHandler_ = function(payload) {
|
||||
var now = goog.now();
|
||||
var args = payload.split(',');
|
||||
var type = args[0];
|
||||
var pageX = args[1];
|
||||
var pageY = args[2];
|
||||
var time = parseInt(args[3], 10);
|
||||
|
||||
var msg = type + ': (' + pageX + ',' + pageY + '), latency: ' + (now - time);
|
||||
xpcdemo.log(msg);
|
||||
|
||||
if (type == goog.events.EventType.MOUSEMOVE) {
|
||||
xpcdemo.mmCount_++;
|
||||
var dt = now - xpcdemo.mmLastRateOutput_;
|
||||
if (dt > 1000) {
|
||||
msg = 'RATE (mousemove/s): ' + (1000 * xpcdemo.mmCount_ / dt);
|
||||
xpcdemo.log(msg);
|
||||
xpcdemo.mmLastRateOutput_ = now;
|
||||
xpcdemo.mmCount_ = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send multiple messages.
|
||||
* @param {number} n The number of messages to send.
|
||||
*/
|
||||
xpcdemo.sendN = function(n) {
|
||||
xpcdemo.count_ || (xpcdemo.count_ = 1);
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
xpcdemo.channel.send('log', '' + xpcdemo.count_++);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,298 @@
|
||||
// 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.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 (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
|
||||
this.creationStack = new Error().stack;
|
||||
goog.Disposable.instances_[goog.getUid(this)] = this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @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 bet 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.Disposable.MONITORING_MODE = 0;
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* Disposables that should be disposed when this object is disposed.
|
||||
* @type {Array.<goog.disposable.IDisposable>}
|
||||
* @private
|
||||
*/
|
||||
goog.Disposable.prototype.dependentDisposables_;
|
||||
|
||||
|
||||
/**
|
||||
* Callbacks to invoke when this object is disposed.
|
||||
* @type {Array.<!Function>}
|
||||
* @private
|
||||
*/
|
||||
goog.Disposable.prototype.onDisposeCallbacks_;
|
||||
|
||||
|
||||
/**
|
||||
* If monitoring the goog.Disposable instances is enabled, stores the creation
|
||||
* stack trace of the Disposable instance.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.Disposable.prototype.creationStack;
|
||||
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
if (!this.dependentDisposables_) {
|
||||
this.dependentDisposables_ = [];
|
||||
}
|
||||
this.dependentDisposables_.push(disposable);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Invokes a callback function when this object is disposed. Callbacks are
|
||||
* invoked in the order in which they were added.
|
||||
* @param {!Function} callback The callback function.
|
||||
* @param {Object=} opt_scope An optional scope to call the callback in.
|
||||
*/
|
||||
goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
|
||||
if (!this.onDisposeCallbacks_) {
|
||||
this.onDisposeCallbacks_ = [];
|
||||
}
|
||||
this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
* goog.base(this);
|
||||
* // 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.
|
||||
* goog.base(this, 'disposeInternal');
|
||||
* };
|
||||
* </pre>
|
||||
* @protected
|
||||
*/
|
||||
goog.Disposable.prototype.disposeInternal = function() {
|
||||
if (this.dependentDisposables_) {
|
||||
goog.disposeAll.apply(null, this.dependentDisposables_);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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 notes, or other disposable objects), it should
|
||||
* implement this interface (it may subclass goog.Disposable).
|
||||
* @interface
|
||||
*/
|
||||
goog.disposable.IDisposable = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the object and its resources.
|
||||
* @return {void} Nothing.
|
||||
*/
|
||||
goog.disposable.IDisposable.prototype.dispose;
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the object has been disposed of.
|
||||
*/
|
||||
goog.disposable.IDisposable.prototype.isDisposed;
|
||||
@@ -0,0 +1,540 @@
|
||||
// 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 Utilities for adding, removing and setting ARIA roles
|
||||
* as defined by W3C ARIA Working Draft:
|
||||
* http://www.w3.org/TR/2010/WD-wai-aria-20100916/
|
||||
* All modern browsers have some form of ARIA support, so no browser checks are
|
||||
* performed when adding ARIA to components.
|
||||
*
|
||||
*/
|
||||
goog.provide('goog.dom.a11y');
|
||||
goog.provide('goog.dom.a11y.Announcer');
|
||||
goog.provide('goog.dom.a11y.LivePriority');
|
||||
goog.provide('goog.dom.a11y.Role');
|
||||
goog.provide('goog.dom.a11y.State');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of ARIA states and properties.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.a11y.State = {
|
||||
// ARIA property for setting the currently active descendant of an element,
|
||||
// for example the selected item in a list box. Value: ID of an element.
|
||||
ACTIVEDESCENDANT: 'activedescendant',
|
||||
|
||||
// ARIA property that, if true, indicates that all of a changed region should
|
||||
// be presented, instead of only parts. Value: one of {true, false}.
|
||||
ATOMIC: 'atomic',
|
||||
|
||||
// ARIA property to specify that input completion is provided. Value:
|
||||
// one of {'inline', 'list', 'both', 'none'}.
|
||||
AUTOCOMPLETE: 'autocomplete',
|
||||
|
||||
// ARIA state to indicate that an element and its subtree are being updated.
|
||||
// Value: one of {true, false}.
|
||||
BUSY: 'busy',
|
||||
|
||||
// ARIA state for a checked item. Value: one of {'true', 'false', 'mixed',
|
||||
// undefined}.
|
||||
CHECKED: 'checked',
|
||||
|
||||
// ARIA property that identifies the element or elements whose contents or
|
||||
// presence are controlled by this element. Value: space-separated IDs of
|
||||
// other elements.
|
||||
CONTROLS: 'controls',
|
||||
|
||||
// ARIA property that identifies the element or elements that describe
|
||||
// this element. Value: space-separated IDs of other elements.
|
||||
DESCRIBEDBY: 'describedby',
|
||||
|
||||
// ARIA state for a disabled item. Value: one of {true, false}.
|
||||
DISABLED: 'disabled',
|
||||
|
||||
// ARIA property that indicates what functions can be performed when a
|
||||
// dragged object is released on the drop target. Value: one of
|
||||
// {'copy', 'move', 'link', 'execute', 'popup', 'none'}.
|
||||
DROPEFFECT: 'dropeffect',
|
||||
|
||||
// ARIA state for setting whether the element like a tree node is expanded.
|
||||
// Value: one of {true, false, undefined}.
|
||||
EXPANDED: 'expanded',
|
||||
|
||||
// ARIA property that identifies the next element (or elements) in the
|
||||
// recommended reading order of content. Value: space-separated ids of
|
||||
// elements to flow to.
|
||||
FLOWTO: 'flowto',
|
||||
|
||||
// ARIA state that indicates an element's "grabbed" state in drag-and-drop.
|
||||
// Value: one of {true, false, undefined}.
|
||||
GRABBED: 'grabbed',
|
||||
|
||||
// ARIA property indicating whether the element has a popup. Value: one of
|
||||
// {true, false}.
|
||||
HASPOPUP: 'haspopup',
|
||||
|
||||
// ARIA state indicating that the element is not visible or perceivable
|
||||
// to any user. Value: one of {true, false}.
|
||||
HIDDEN: 'hidden',
|
||||
|
||||
// ARIA state indicating that the entered value does not conform. Value:
|
||||
// one of {false, true, 'grammar', 'spelling'}
|
||||
INVALID: 'invalid',
|
||||
|
||||
// ARIA property that provides a label to override any other text, value, or
|
||||
// contents used to describe this element. Value: string.
|
||||
LABEL: 'label',
|
||||
|
||||
// ARIA property for setting the element which labels another element.
|
||||
// Value: space-separated IDs of elements.
|
||||
LABELLEDBY: 'labelledby',
|
||||
|
||||
// ARIA property for setting the level of an element in the hierarchy.
|
||||
// Value: integer.
|
||||
LEVEL: 'level',
|
||||
|
||||
// ARIA property indicating that an element will be updated, and
|
||||
// describes the types of updates the user agents, assistive technologies,
|
||||
// and user can expect from the live region. Value: one of {'off', 'polite',
|
||||
// 'assertive'}.
|
||||
LIVE: 'live',
|
||||
|
||||
// ARIA property indicating whether a text box can accept multiline input.
|
||||
// Value: one of {true, false}.
|
||||
MULTILINE: 'multiline',
|
||||
|
||||
// ARIA property indicating if the user may select more than one item.
|
||||
// Value: one of {true, false}.
|
||||
MULTISELECTABLE: 'multiselectable',
|
||||
|
||||
// ARIA property indicating if the element is horizontal or vertical.
|
||||
// Value: one of {'vertical', 'horizontal'}.
|
||||
ORIENTATION: 'orientation',
|
||||
|
||||
// ARIA property creating a visual, functional, or contextual parent/child
|
||||
// relationship when the DOM hierarchy can't be used to represent it.
|
||||
// Value: Space-separated IDs of elements.
|
||||
OWNS: 'owns',
|
||||
|
||||
// ARIA property that defines an element's number of position in a list.
|
||||
// Value: integer.
|
||||
POSINSET: 'posinset',
|
||||
|
||||
// ARIA state for a pressed item. Value: one of {true, false, undefined,
|
||||
// 'mixed'}.
|
||||
PRESSED: 'pressed',
|
||||
|
||||
// ARIA property indicating that an element is not editable. Value:
|
||||
// one of {true, false}.
|
||||
READONLY: 'readonly',
|
||||
|
||||
// ARIA property indicating that change notifications within this subtree
|
||||
// of a live region should be announced. Value: one of {'additions',
|
||||
// 'removals', 'text', 'all', 'additions text'}.
|
||||
RELEVANT: 'relevant',
|
||||
|
||||
// ARIA property indicating that user input is required on this element
|
||||
// before a form may be submitted. Value: one of {true, false}.
|
||||
REQUIRED: 'required',
|
||||
|
||||
// ARIA state for setting the currently selected item in the list.
|
||||
// Value: one of {true, false, undefined}.
|
||||
SELECTED: 'selected',
|
||||
|
||||
// ARIA property defining the number of items in a list. Value: integer.
|
||||
SETSIZE: 'setsize',
|
||||
|
||||
// ARIA property indicating if items are sorted. Value: one of {'ascending',
|
||||
// 'descending', 'none', 'other'}.
|
||||
SORT: 'sort',
|
||||
|
||||
// ARIA property for slider maximum value. Value: number.
|
||||
VALUEMAX: 'valuemax',
|
||||
|
||||
// ARIA property for slider minimum value. Value: number.
|
||||
VALUEMIN: 'valuemin',
|
||||
|
||||
// ARIA property for slider active value. Value: number.
|
||||
VALUENOW: 'valuenow',
|
||||
|
||||
// ARIA property for slider active value represented as text. Value: string.
|
||||
VALUETEXT: 'valuetext'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of ARIA roles.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.a11y.Role = {
|
||||
// ARIA role for an alert element that doesn't need to be explicitly closed.
|
||||
ALERT: 'alert',
|
||||
|
||||
// ARIA role for an alert dialog element that takes focus and must be closed.
|
||||
ALERTDIALOG: 'alertdialog',
|
||||
|
||||
// ARIA role for an application that implements its own keyboard navigation.
|
||||
APPLICATION: 'application',
|
||||
|
||||
// ARIA role for an article.
|
||||
ARTICLE: 'article',
|
||||
|
||||
// ARIA role for a banner containing mostly site content, not page content.
|
||||
BANNER: 'banner',
|
||||
|
||||
// ARIA role for a button element.
|
||||
BUTTON: 'button',
|
||||
|
||||
// ARIA role for a checkbox button element; use with the CHECKED state.
|
||||
CHECKBOX: 'checkbox',
|
||||
|
||||
// ARIA role for a column header of a table or grid.
|
||||
COLUMNHEADER: 'columnheader',
|
||||
|
||||
// ARIA role for a combo box element.
|
||||
COMBOBOX: 'combobox',
|
||||
|
||||
// ARIA role for a supporting section of the document.
|
||||
COMPLEMENTARY: 'complementary',
|
||||
|
||||
// ARIA role for a dialog, some descendant must take initial focus.
|
||||
DIALOG: 'dialog',
|
||||
|
||||
// ARIA role for a directory, like a table of contents.
|
||||
DIRECTORY: 'directory',
|
||||
|
||||
// ARIA role for a part of a page that's a document, not a web application.
|
||||
DOCUMENT: 'document',
|
||||
|
||||
// ARIA role for a landmark region logically considered one form.
|
||||
FORM: 'form',
|
||||
|
||||
// ARIA role for an interactive control of tabular data.
|
||||
GRID: 'grid',
|
||||
|
||||
// ARIA role for a cell in a grid.
|
||||
GRIDCELL: 'gridcell',
|
||||
|
||||
// ARIA role for a group of related elements like tree item siblings.
|
||||
GROUP: 'group',
|
||||
|
||||
// ARIA role for a heading element.
|
||||
HEADING: 'heading',
|
||||
|
||||
// ARIA role for a container of elements that together comprise one image.
|
||||
IMG: 'img',
|
||||
|
||||
// ARIA role for a link.
|
||||
LINK: 'link',
|
||||
|
||||
// ARIA role for a list of non-interactive list items.
|
||||
LIST: 'list',
|
||||
|
||||
// ARIA role for a listbox.
|
||||
LISTBOX: 'listbox',
|
||||
|
||||
// ARIA role for a list item.
|
||||
LISTITEM: 'listitem',
|
||||
|
||||
// ARIA role for a live region where new information is added.
|
||||
LOG: 'log',
|
||||
|
||||
// ARIA landmark role for the main content in a document. Use only once.
|
||||
MAIN: 'main',
|
||||
|
||||
// ARIA role for a live region of non-essential information that changes.
|
||||
MARQUEE: 'marquee',
|
||||
|
||||
// ARIA role for a mathematical expression.
|
||||
MATH: 'math',
|
||||
|
||||
// ARIA role for a popup menu.
|
||||
MENU: 'menu',
|
||||
|
||||
// ARIA role for a menubar element containing menu elements.
|
||||
MENUBAR: 'menubar',
|
||||
|
||||
// ARIA role for menu item elements.
|
||||
MENU_ITEM: 'menuitem',
|
||||
|
||||
// ARIA role for a checkbox box element inside a menu.
|
||||
MENU_ITEM_CHECKBOX: 'menuitemcheckbox',
|
||||
|
||||
// ARIA role for a radio button element inside a menu.
|
||||
MENU_ITEM_RADIO: 'menuitemradio',
|
||||
|
||||
// ARIA landmark role for a collection of navigation links.
|
||||
NAVIGATION: 'navigation',
|
||||
|
||||
// ARIA role for a section ancillary to the main content.
|
||||
NOTE: 'note',
|
||||
|
||||
// ARIA role for option items that are children of combobox, listbox, menu,
|
||||
// radiogroup, or tree elements.
|
||||
OPTION: 'option',
|
||||
|
||||
// ARIA role for ignorable cosmetic elements with no semantic significance.
|
||||
PRESENTATION: 'presentation',
|
||||
|
||||
// ARIA role for a progress bar element.
|
||||
PROGRESSBAR: 'progressbar',
|
||||
|
||||
// ARIA role for a radio button element.
|
||||
RADIO: 'radio',
|
||||
|
||||
// ARIA role for a group of connected radio button elements.
|
||||
RADIOGROUP: 'radiogroup',
|
||||
|
||||
// ARIA role for an important region of the page.
|
||||
REGION: 'region',
|
||||
|
||||
// ARIA role for a row of cells in a grid.
|
||||
ROW: 'row',
|
||||
|
||||
// ARIA role for a group of one or more rows in a grid.
|
||||
ROWGROUP: 'rowgroup',
|
||||
|
||||
// ARIA role for a row header of a table or grid.
|
||||
ROWHEADER: 'rowheader',
|
||||
|
||||
// ARIA role for a scrollbar element.
|
||||
SCROLLBAR: 'scrollbar',
|
||||
|
||||
// ARIA landmark role for a part of the page providing search functionality.
|
||||
SEARCH: 'search',
|
||||
|
||||
// ARIA role for a menu separator.
|
||||
SEPARATOR: 'separator',
|
||||
|
||||
// ARIA role for a slider.
|
||||
SLIDER: 'slider',
|
||||
|
||||
// ARIA role for a spin button.
|
||||
SPINBUTTON: 'spinbutton',
|
||||
|
||||
// ARIA role for a live region with advisory info less severe than an alert.
|
||||
STATUS: 'status',
|
||||
|
||||
// ARIA role for a tab button.
|
||||
TAB: 'tab',
|
||||
|
||||
// ARIA role for a tab bar (i.e. a list of tab buttons).
|
||||
TAB_LIST: 'tablist',
|
||||
|
||||
// ARIA role for a tab page (i.e. the element holding tab contents).
|
||||
TAB_PANEL: 'tabpanel',
|
||||
|
||||
// ARIA role for a textbox element.
|
||||
TEXTBOX: 'textbox',
|
||||
|
||||
// ARIA role for an element displaying elapsed time or time remaining.
|
||||
TIMER: 'timer',
|
||||
|
||||
// ARIA role for a toolbar element.
|
||||
TOOLBAR: 'toolbar',
|
||||
|
||||
// ARIA role for a tooltip element.
|
||||
TOOLTIP: 'tooltip',
|
||||
|
||||
// ARIA role for a tree.
|
||||
TREE: 'tree',
|
||||
|
||||
// ARIA role for a grid whose rows can be expanded and collapsed like a tree.
|
||||
TREEGRID: 'treegrid',
|
||||
|
||||
// ARIA role for a tree item that sometimes may be expanded or collapsed.
|
||||
TREEITEM: 'treeitem'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of ARIA state values for live regions.
|
||||
*
|
||||
* See http://www.w3.org/TR/wai-aria/states_and_properties#aria-live
|
||||
* for more information.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.a11y.LivePriority = {
|
||||
/**
|
||||
* Default value. Used for live regions that should never be spoken.
|
||||
*/
|
||||
OFF: 'off',
|
||||
/**
|
||||
* Spoke only when the user is idle. Best option in most cases.
|
||||
*/
|
||||
POLITE: 'polite',
|
||||
/**
|
||||
* Spoken as soon as possible, which means that the information has a
|
||||
* higher priority than normal, but does not necessarily interrupt
|
||||
* immediately.
|
||||
*/
|
||||
ASSERTIVE: 'assertive'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the role of an element.
|
||||
* @param {Element} element DOM node to set role of.
|
||||
* @param {string} roleName role name(s).
|
||||
*/
|
||||
goog.dom.a11y.setRole = function(element, roleName) {
|
||||
element.setAttribute('role', roleName);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets role of an element.
|
||||
* @param {Element} element DOM node to get role of.
|
||||
* @return {string} rolename.
|
||||
*/
|
||||
goog.dom.a11y.getRole = function(element) {
|
||||
return element.getAttribute('role') || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the state or property of an element.
|
||||
* @param {Element} element DOM node where we set state.
|
||||
* @param {string} state State attribute being set. Automatically adds prefix
|
||||
* 'aria-' to the state name.
|
||||
* @param {string|boolean|number} value Value for the state attribute.
|
||||
*/
|
||||
goog.dom.a11y.setState = function(element, state, value) {
|
||||
element.setAttribute('aria-' + state, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets value of specified state or property.
|
||||
* @param {Element} element DOM node to get state from.
|
||||
* @param {string} stateName State name.
|
||||
* @return {string} Value of the state attribute.
|
||||
*/
|
||||
goog.dom.a11y.getState = function(element, stateName) {
|
||||
var attrb =
|
||||
/** @type {string|number|boolean} */(element.getAttribute('aria-' +
|
||||
stateName));
|
||||
// Check for multiple representations - attrb might
|
||||
// be a boolean or a string
|
||||
if ((attrb === true) || (attrb === false)) {
|
||||
return attrb ? 'true' : 'false';
|
||||
} else if (!attrb) {
|
||||
return '';
|
||||
} else {
|
||||
return String(attrb);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the activedescendant of the given element.
|
||||
* @param {Element} element DOM node to get activedescendant from.
|
||||
* @return {Element} DOM node of the activedescendant.
|
||||
*/
|
||||
goog.dom.a11y.getActiveDescendant = function(element) {
|
||||
var id = goog.dom.a11y.getState(
|
||||
element, goog.dom.a11y.State.ACTIVEDESCENDANT);
|
||||
return goog.dom.getOwnerDocument(element).getElementById(id);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the activedescendant value for an element.
|
||||
* @param {Element} element DOM node to set activedescendant to.
|
||||
* @param {Element} activeElement DOM node being set as activedescendant.
|
||||
*/
|
||||
goog.dom.a11y.setActiveDescendant = function(element, activeElement) {
|
||||
goog.dom.a11y.setState(element, goog.dom.a11y.State.ACTIVEDESCENDANT,
|
||||
activeElement ? activeElement.id : '');
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class that allows messages to be spoken by assistive technologies that the
|
||||
* user may have active.
|
||||
*
|
||||
* @param {goog.dom.DomHelper} domHelper DOM helper.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.dom.a11y.Announcer = function(domHelper) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.domHelper_ = domHelper;
|
||||
|
||||
/**
|
||||
* Map of priority to live region elements to use for communicating updates.
|
||||
* Elements are created on demand.
|
||||
* @type {Object.<goog.dom.a11y.LivePriority, Element>}
|
||||
* @private
|
||||
*/
|
||||
this.liveRegions_ = {};
|
||||
};
|
||||
goog.inherits(goog.dom.a11y.Announcer, goog.Disposable);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.a11y.Announcer.prototype.disposeInternal = function() {
|
||||
goog.object.forEach(
|
||||
this.liveRegions_, this.domHelper_.removeNode, this.domHelper_);
|
||||
this.liveRegions_ = null;
|
||||
this.domHelper_ = null;
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Announce a message to be read by any assistive technologies the user may
|
||||
* have active.
|
||||
* @param {string} message The message to announce to screen readers.
|
||||
* @param {goog.dom.a11y.LivePriority=} opt_priority The priority of the
|
||||
* message. Defaults to POLITE.
|
||||
*/
|
||||
goog.dom.a11y.Announcer.prototype.say = function(message, opt_priority) {
|
||||
goog.dom.setTextContent(this.getLiveRegion_(
|
||||
opt_priority || goog.dom.a11y.LivePriority.POLITE), message);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an aria-live region that can be used to communicate announcements.
|
||||
* @param {goog.dom.a11y.LivePriority} priority The required priority.
|
||||
* @return {Element} A live region of the requested priority.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.a11y.Announcer.prototype.getLiveRegion_ = function(priority) {
|
||||
if (this.liveRegions_[priority]) {
|
||||
return this.liveRegions_[priority];
|
||||
}
|
||||
var liveRegion;
|
||||
liveRegion = this.domHelper_.createElement('div');
|
||||
liveRegion.style.position = 'absolute';
|
||||
liveRegion.style.top = '-1000px';
|
||||
goog.dom.a11y.setState(liveRegion, 'live', priority);
|
||||
goog.dom.a11y.setState(liveRegion, 'atomic', 'true');
|
||||
this.domHelper_.getDocument().body.appendChild(liveRegion);
|
||||
this.liveRegions_[priority] = liveRegion;
|
||||
return liveRegion;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 working with ranges comprised of multiple
|
||||
* sub-ranges.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.AbstractMultiRange');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.AbstractRange');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new multi range with no properties. Do not use this
|
||||
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
|
||||
* @constructor
|
||||
* @extends {goog.dom.AbstractRange}
|
||||
*/
|
||||
goog.dom.AbstractMultiRange = function() {
|
||||
};
|
||||
goog.inherits(goog.dom.AbstractMultiRange, goog.dom.AbstractRange);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.AbstractMultiRange.prototype.containsRange = function(
|
||||
otherRange, opt_allowPartial) {
|
||||
// TODO(user): This will incorrectly return false if two (or more) adjacent
|
||||
// elements are both in the control range, and are also in the text range
|
||||
// being compared to.
|
||||
var ranges = this.getTextRanges();
|
||||
var otherRanges = otherRange.getTextRanges();
|
||||
|
||||
var fn = opt_allowPartial ? goog.array.some : goog.array.every;
|
||||
return fn(otherRanges, function(otherRange) {
|
||||
return goog.array.some(ranges, function(range) {
|
||||
return range.containsRange(otherRange, opt_allowPartial);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.AbstractMultiRange.prototype.insertNode = function(node, before) {
|
||||
if (before) {
|
||||
goog.dom.insertSiblingBefore(node, this.getStartNode());
|
||||
} else {
|
||||
goog.dom.insertSiblingAfter(node, this.getEndNode());
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.AbstractMultiRange.prototype.surroundWithNodes = function(startNode,
|
||||
endNode) {
|
||||
this.insertNode(startNode, true);
|
||||
this.insertNode(endNode, false);
|
||||
};
|
||||
@@ -0,0 +1,514 @@
|
||||
// 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 Interface definitions for working with ranges
|
||||
* in HTML documents.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.AbstractRange');
|
||||
goog.provide('goog.dom.RangeIterator');
|
||||
goog.provide('goog.dom.RangeType');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.SavedCaretRange');
|
||||
goog.require('goog.dom.TagIterator');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Types of ranges.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.RangeType = {
|
||||
TEXT: 'text',
|
||||
CONTROL: 'control',
|
||||
MULTI: 'mutli'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new selection with no properties. Do not use this constructor -
|
||||
* use one of the goog.dom.Range.from* methods instead.
|
||||
* @constructor
|
||||
*/
|
||||
goog.dom.AbstractRange = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the browser native selection object from the given window.
|
||||
* @param {Window} win The window to get the selection object from.
|
||||
* @return {Object} The browser native selection object, or null if it could
|
||||
* not be retrieved.
|
||||
*/
|
||||
goog.dom.AbstractRange.getBrowserSelectionForWindow = function(win) {
|
||||
if (win.getSelection) {
|
||||
// W3C
|
||||
return win.getSelection();
|
||||
} else {
|
||||
// IE
|
||||
var doc = win.document;
|
||||
var sel = doc.selection;
|
||||
if (sel) {
|
||||
// IE has a bug where it sometimes returns a selection from the wrong
|
||||
// document. Catching these cases now helps us avoid problems later.
|
||||
try {
|
||||
var range = sel.createRange();
|
||||
// Only TextRanges have a parentElement method.
|
||||
if (range.parentElement) {
|
||||
if (range.parentElement().document != doc) {
|
||||
return null;
|
||||
}
|
||||
} else if (!range.length || range.item(0).document != doc) {
|
||||
// For ControlRanges, check that the range has items, and that
|
||||
// the first item in the range is in the correct document.
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
// If the selection is in the wrong document, and the wrong document is
|
||||
// in a different domain, IE will throw an exception.
|
||||
return null;
|
||||
}
|
||||
// TODO(user|robbyw) Sometimes IE 6 returns a selection instance
|
||||
// when there is no selection. This object has a 'type' property equals
|
||||
// to 'None' and a typeDetail property bound to undefined. Ideally this
|
||||
// function should not return this instance.
|
||||
return sel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests if the given Object is a controlRange.
|
||||
* @param {Object} range The range object to test.
|
||||
* @return {boolean} Whether the given Object is a controlRange.
|
||||
*/
|
||||
goog.dom.AbstractRange.isNativeControlRange = function(range) {
|
||||
// For now, tests for presence of a control range function.
|
||||
return !!range && !!range.addElement;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.AbstractRange} A clone of this range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.clone = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.RangeType} The type of range represented by this object.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getType = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Range|TextRange} The native browser range object.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getBrowserRangeObject = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the native browser range object, overwriting any state this range was
|
||||
* storing.
|
||||
* @param {Range|TextRange} nativeRange The native browser range object.
|
||||
* @return {boolean} Whether the given range was accepted. If not, the caller
|
||||
* will need to call goog.dom.Range.createFromBrowserRange to create a new
|
||||
* range object.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.setBrowserRangeObject = function(nativeRange) {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The number of text ranges in this range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getTextRangeCount = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Get the i-th text range in this range. The behavior is undefined if
|
||||
* i >= getTextRangeCount or i < 0.
|
||||
* @param {number} i The range number to retrieve.
|
||||
* @return {goog.dom.TextRange} The i-th text range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getTextRange = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Gets an array of all text ranges this range is comprised of. For non-multi
|
||||
* ranges, returns a single element array containing this.
|
||||
* @return {Array.<goog.dom.TextRange>} Array of text ranges.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getTextRanges = function() {
|
||||
var output = [];
|
||||
for (var i = 0, len = this.getTextRangeCount(); i < len; i++) {
|
||||
output.push(this.getTextRange(i));
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The deepest node that contains the entire range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getContainer = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the deepest element in the tree that contains the entire range.
|
||||
* @return {Element} The deepest element that contains the entire range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getContainerElement = function() {
|
||||
var node = this.getContainer();
|
||||
return /** @type {Element} */ (
|
||||
node.nodeType == goog.dom.NodeType.ELEMENT ? node : node.parentNode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The element or text node the range starts in. For text
|
||||
* ranges, the range comprises all text between the start and end position.
|
||||
* For other types of range, start and end give bounds of the range but
|
||||
* do not imply all nodes in those bounds are selected.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getStartNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The offset into the node the range starts in. For text
|
||||
* nodes, this is an offset into the node value. For elements, this is
|
||||
* an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getStartOffset = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The element or text node the range ends in.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getEndNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The offset into the node the range ends in. For text
|
||||
* nodes, this is an offset into the node value. For elements, this is
|
||||
* an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getEndOffset = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The element or text node the range is anchored at.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getAnchorNode = function() {
|
||||
return this.isReversed() ? this.getEndNode() : this.getStartNode();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The offset into the node the range is anchored at. For
|
||||
* text nodes, this is an offset into the node value. For elements, this
|
||||
* is an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getAnchorOffset = function() {
|
||||
return this.isReversed() ? this.getEndOffset() : this.getStartOffset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The element or text node the range is focused at - i.e. where
|
||||
* the cursor is.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getFocusNode = function() {
|
||||
return this.isReversed() ? this.getStartNode() : this.getEndNode();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The offset into the node the range is focused at - i.e.
|
||||
* where the cursor is. For text nodes, this is an offset into the node
|
||||
* value. For elements, this is an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getFocusOffset = function() {
|
||||
return this.isReversed() ? this.getStartOffset() : this.getEndOffset();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the selection is reversed.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.isReversed = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Document} The document this selection is a part of.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getDocument = function() {
|
||||
// Using start node in IE was crashing the browser in some cases so use
|
||||
// getContainer for that browser. It's also faster for IE, but still slower
|
||||
// than start node for other browsers so we continue to use getStartNode when
|
||||
// it is not problematic. See bug 1687309.
|
||||
return goog.dom.getOwnerDocument(goog.userAgent.IE ?
|
||||
this.getContainer() : this.getStartNode());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Window} The window this selection is a part of.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getWindow = function() {
|
||||
return goog.dom.getWindow(this.getDocument());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests if this range contains the given range.
|
||||
* @param {goog.dom.AbstractRange} range The range to test.
|
||||
* @param {boolean=} opt_allowPartial If true, the range can be partially
|
||||
* contained in the selection, otherwise the range must be entirely
|
||||
* contained.
|
||||
* @return {boolean} Whether this range contains the given range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.containsRange = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Tests if this range contains the given node.
|
||||
* @param {Node} node The node to test for.
|
||||
* @param {boolean=} opt_allowPartial If not set or false, the node must be
|
||||
* entirely contained in the selection for this function to return true.
|
||||
* @return {boolean} Whether this range contains the given node.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.containsNode = function(node,
|
||||
opt_allowPartial) {
|
||||
return this.containsRange(goog.dom.Range.createFromNodeContents(node),
|
||||
opt_allowPartial);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this range is valid (i.e. whether its endpoints are still in
|
||||
* the document). A range becomes invalid when, after this object was created,
|
||||
* either one or both of its endpoints are removed from the document. Use of
|
||||
* an invalid range can lead to runtime errors, particularly in IE.
|
||||
* @return {boolean} Whether the range is valid.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.isRangeInDocument = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the range is collapsed.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.isCollapsed = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The text content of the range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getText = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the HTML fragment this range selects. This is slow on all browsers.
|
||||
* The HTML fragment may not be valid HTML, for instance if the user selects
|
||||
* from a to b inclusively in the following html:
|
||||
*
|
||||
* >div<a>/div<b
|
||||
*
|
||||
* This method will return
|
||||
*
|
||||
* a</div>b
|
||||
*
|
||||
* If you need valid HTML, use {@link #getValidHtml} instead.
|
||||
*
|
||||
* @return {string} HTML fragment of the range, does not include context
|
||||
* containing elements.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getHtmlFragment = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns valid HTML for this range. This is fast on IE, and semi-fast on
|
||||
* other browsers.
|
||||
* @return {string} Valid HTML of the range, including context containing
|
||||
* elements.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getValidHtml = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns pastable HTML for this range. This guarantees that any child items
|
||||
* that must have specific ancestors will have them, for instance all TDs will
|
||||
* be contained in a TR in a TBODY in a TABLE and all LIs will be contained in
|
||||
* a UL or OL as appropriate. This is semi-fast on all browsers.
|
||||
* @return {string} Pastable HTML of the range, including context containing
|
||||
* elements.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.getPastableHtml = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a RangeIterator over the contents of the range. Regardless of the
|
||||
* direction of the range, the iterator will move in document order.
|
||||
* @param {boolean=} opt_keys Unused for this iterator.
|
||||
* @return {goog.dom.RangeIterator} An iterator over tags in the range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.__iterator__ = goog.abstractMethod;
|
||||
|
||||
|
||||
// RANGE ACTIONS
|
||||
|
||||
|
||||
/**
|
||||
* Sets this range as the selection in its window.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.select = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Removes the contents of the range from the document.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.removeContents = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a node before (or after) the range. The range may be disrupted
|
||||
* beyond recovery because of the way this splits nodes.
|
||||
* @param {Node} node The node to insert.
|
||||
* @param {boolean} before True to insert before, false to insert after.
|
||||
* @return {Node} The node added to the document. This may be different
|
||||
* than the node parameter because on IE we have to clone it.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.insertNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the range contents with (possibly a copy of) the given node. The
|
||||
* range may be disrupted beyond recovery because of the way this splits nodes.
|
||||
* @param {Node} node The node to insert.
|
||||
* @return {Node} The node added to the document. This may be different
|
||||
* than the node parameter because on IE we have to clone it.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.replaceContentsWithNode = function(node) {
|
||||
if (!this.isCollapsed()) {
|
||||
this.removeContents();
|
||||
}
|
||||
|
||||
return this.insertNode(node, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Surrounds this range with the two given nodes. The range may be disrupted
|
||||
* beyond recovery because of the way this splits nodes.
|
||||
* @param {Element} startNode The node to insert at the start.
|
||||
* @param {Element} endNode The node to insert at the end.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.surroundWithNodes = goog.abstractMethod;
|
||||
|
||||
|
||||
// SAVE/RESTORE
|
||||
|
||||
|
||||
/**
|
||||
* Saves the range so that if the start and end nodes are left alone, it can
|
||||
* be restored.
|
||||
* @return {goog.dom.SavedRange} A range representation that can be restored
|
||||
* as long as the endpoint nodes of the selection are not modified.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.saveUsingDom = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Saves the range using HTML carets. As long as the carets remained in the
|
||||
* HTML, the range can be restored...even when the HTML is copied across
|
||||
* documents.
|
||||
* @return {goog.dom.SavedCaretRange?} A range representation that can be
|
||||
* restored as long as carets are not removed. Returns null if carets
|
||||
* could not be created.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.saveUsingCarets = function() {
|
||||
return (this.getStartNode() && this.getEndNode()) ?
|
||||
new goog.dom.SavedCaretRange(this) : null;
|
||||
};
|
||||
|
||||
|
||||
// RANGE MODIFICATION
|
||||
|
||||
|
||||
/**
|
||||
* Collapses the range to one of its boundary points.
|
||||
* @param {boolean} toAnchor Whether to collapse to the anchor of the range.
|
||||
*/
|
||||
goog.dom.AbstractRange.prototype.collapse = goog.abstractMethod;
|
||||
|
||||
// RANGE ITERATION
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
|
||||
* adds functions to determine the portion of each text node that is selected.
|
||||
* @param {Node} node The node to start traversal at. When null, creates an
|
||||
* empty iterator.
|
||||
* @param {boolean=} opt_reverse Whether to traverse nodes in reverse.
|
||||
* @constructor
|
||||
* @extends {goog.dom.TagIterator}
|
||||
*/
|
||||
goog.dom.RangeIterator = function(node, opt_reverse) {
|
||||
goog.dom.TagIterator.call(this, node, opt_reverse, true);
|
||||
};
|
||||
goog.inherits(goog.dom.RangeIterator, goog.dom.TagIterator);
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The offset into the current node, or -1 if the current node
|
||||
* is not a text node.
|
||||
*/
|
||||
goog.dom.RangeIterator.prototype.getStartTextOffset = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The end offset into the current node, or -1 if the current
|
||||
* node is not a text node.
|
||||
*/
|
||||
goog.dom.RangeIterator.prototype.getEndTextOffset = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} node The iterator's start node.
|
||||
*/
|
||||
goog.dom.RangeIterator.prototype.getStartNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {Node} The iterator's end node.
|
||||
*/
|
||||
goog.dom.RangeIterator.prototype.getEndNode = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether a call to next will fail.
|
||||
*/
|
||||
goog.dom.RangeIterator.prototype.isLast = goog.abstractMethod;
|
||||
@@ -0,0 +1,355 @@
|
||||
// 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 Methods for annotating occurrences of query terms in text or
|
||||
* in a DOM tree. Adapted from Gmail code.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.annotate');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Calls {@code annotateFn} for each occurrence of a search term in text nodes
|
||||
* under {@code node}. Returns the number of hits.
|
||||
*
|
||||
* @param {Node} node A DOM node.
|
||||
* @param {Array} terms An array of [searchTerm, matchWholeWordOnly] tuples.
|
||||
* The matchWholeWordOnly value is a per-term attribute because some terms
|
||||
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
|
||||
* should always be false for CJK terms.).
|
||||
* @param {Function} annotateFn A function that takes
|
||||
* (1) the number of the term that is "hit",
|
||||
* (2) the HTML string (search term) to be annotated,
|
||||
* and returns the annotated term as an HTML string.
|
||||
* @param {*=} opt_ignoreCase Whether to ignore the case of the query
|
||||
* terms when looking for matches.
|
||||
* @param {Array.<string>=} opt_classesToSkip Nodes with one of these CSS class
|
||||
* names (and its descendants) will be skipped.
|
||||
* @param {number=} opt_maxMs Number of milliseconds after which this function,
|
||||
* if still annotating, should stop and return.
|
||||
*
|
||||
* @return {boolean} Whether any terms were annotated.
|
||||
*/
|
||||
goog.dom.annotate.annotateTerms = function(node, terms, annotateFn,
|
||||
opt_ignoreCase,
|
||||
opt_classesToSkip,
|
||||
opt_maxMs) {
|
||||
if (opt_ignoreCase) {
|
||||
terms = goog.dom.annotate.lowercaseTerms_(terms);
|
||||
}
|
||||
var stopTime = opt_maxMs > 0 ? goog.now() + opt_maxMs : 0;
|
||||
|
||||
return goog.dom.annotate.annotateTermsInNode_(
|
||||
node, terms, annotateFn, opt_ignoreCase, opt_classesToSkip || [],
|
||||
stopTime, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The maximum recursion depth allowed. Any DOM nodes deeper than this are
|
||||
* ignored.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.MAX_RECURSION_ = 200;
|
||||
|
||||
|
||||
/**
|
||||
* The node types whose descendants should not be affected by annotation.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.NODES_TO_SKIP_ = ['SCRIPT', 'STYLE', 'TEXTAREA'];
|
||||
|
||||
|
||||
/**
|
||||
* Recursive helper function.
|
||||
*
|
||||
* @param {Node} node A DOM node.
|
||||
* @param {Array} terms An array of [searchTerm, matchWholeWordOnly] tuples.
|
||||
* The matchWholeWordOnly value is a per-term attribute because some terms
|
||||
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
|
||||
* should always be false for CJK terms.).
|
||||
* @param {Function} annotateFn function(number, string) : string A function
|
||||
* that takes :
|
||||
* (1) the number of the term that is "hit",
|
||||
* (2) the HTML string (search term) to be annotated,
|
||||
* and returns the annotated term as an HTML string.
|
||||
* @param {*} ignoreCase Whether to ignore the case of the query terms
|
||||
* when looking for matches.
|
||||
* @param {Array.<string>} classesToSkip Nodes with one of these CSS class
|
||||
* names will be skipped (as will their descendants).
|
||||
* @param {number} stopTime Deadline for annotation operation (ignored if 0).
|
||||
* @param {number} recursionLevel How deep this recursive call is; pass the
|
||||
* value 0 in the initial call.
|
||||
* @return {boolean} Whether any terms were annotated.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.annotateTermsInNode_ =
|
||||
function(node, terms, annotateFn, ignoreCase, classesToSkip,
|
||||
stopTime, recursionLevel) {
|
||||
if ((stopTime > 0 && goog.now() >= stopTime) ||
|
||||
recursionLevel > goog.dom.annotate.MAX_RECURSION_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var annotated = false;
|
||||
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
var html = goog.dom.annotate.helpAnnotateText_(node.nodeValue, terms,
|
||||
annotateFn, ignoreCase);
|
||||
if (html != null) {
|
||||
// Replace the text with the annotated html. First we put the html into
|
||||
// a temporary node, to get its DOM structure. To avoid adding a wrapper
|
||||
// element as a side effect, we'll only actually use the temporary node's
|
||||
// children.
|
||||
var tempNode = goog.dom.getOwnerDocument(node).createElement('SPAN');
|
||||
tempNode.innerHTML = html;
|
||||
|
||||
var parentNode = node.parentNode;
|
||||
var nodeToInsert;
|
||||
while ((nodeToInsert = tempNode.firstChild) != null) {
|
||||
// Each parentNode.insertBefore call removes the inserted node from
|
||||
// tempNode's list of children.
|
||||
parentNode.insertBefore(nodeToInsert, node);
|
||||
}
|
||||
|
||||
parentNode.removeChild(node);
|
||||
annotated = true;
|
||||
}
|
||||
} else if (node.hasChildNodes() &&
|
||||
!goog.array.contains(goog.dom.annotate.NODES_TO_SKIP_,
|
||||
node.tagName)) {
|
||||
var classes = node.className.split(/\s+/);
|
||||
var skip = goog.array.some(classes, function(className) {
|
||||
return goog.array.contains(classesToSkip, className);
|
||||
});
|
||||
|
||||
if (!skip) {
|
||||
++recursionLevel;
|
||||
var curNode = node.firstChild;
|
||||
var numTermsAnnotated = 0;
|
||||
while (curNode) {
|
||||
var nextNode = curNode.nextSibling;
|
||||
var curNodeAnnotated = goog.dom.annotate.annotateTermsInNode_(
|
||||
curNode, terms, annotateFn, ignoreCase, classesToSkip,
|
||||
stopTime, recursionLevel);
|
||||
annotated = annotated || curNodeAnnotated;
|
||||
curNode = nextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return annotated;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression that matches non-word characters.
|
||||
*
|
||||
* Performance note: Testing a one-character string using this regex is as fast
|
||||
* as the equivalent string test ("a-zA-Z0-9_".indexOf(c) < 0), give or take a
|
||||
* few percent. (The regex is about 5% faster in IE 6 and about 4% slower in
|
||||
* Firefox 1.5.) If performance becomes critical, it may be better to convert
|
||||
* the character to a numerical char code and check whether it falls in the
|
||||
* word character ranges. A quick test suggests that could be 33% faster.
|
||||
*
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.NONWORD_RE_ = /\W/;
|
||||
|
||||
|
||||
/**
|
||||
* Annotates occurrences of query terms in plain text. This process consists of
|
||||
* identifying all occurrences of all query terms, calling a provided function
|
||||
* to get the appropriate replacement HTML for each occurrence, and
|
||||
* HTML-escaping all the text.
|
||||
*
|
||||
* @param {string} text The plain text to be searched.
|
||||
* @param {Array} terms An array of
|
||||
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
|
||||
* The matchWholeWordOnly value is a per-term attribute because some terms
|
||||
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
|
||||
* should always be false for CJK terms.).
|
||||
* @param {Function} annotateFn {function(number, string) : string} A function
|
||||
* that takes
|
||||
* (1) the number of the term that is "hit",
|
||||
* (2) the HTML string (search term) to be annotated,
|
||||
* and returns the annotated term as an HTML string.
|
||||
* @param {*=} opt_ignoreCase Whether to ignore the case of the query
|
||||
* terms when looking for matches.
|
||||
* @return {?string} The HTML equivalent of {@code text} with terms
|
||||
* annotated, or null if the text did not contain any of the terms.
|
||||
*/
|
||||
goog.dom.annotate.annotateText = function(text, terms, annotateFn,
|
||||
opt_ignoreCase) {
|
||||
if (opt_ignoreCase) {
|
||||
terms = goog.dom.annotate.lowercaseTerms_(terms);
|
||||
}
|
||||
return goog.dom.annotate.helpAnnotateText_(text, terms, annotateFn,
|
||||
opt_ignoreCase);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Annotates occurrences of query terms in plain text. This process consists of
|
||||
* identifying all occurrences of all query terms, calling a provided function
|
||||
* to get the appropriate replacement HTML for each occurrence, and
|
||||
* HTML-escaping all the text.
|
||||
*
|
||||
* @param {string} text The plain text to be searched.
|
||||
* @param {Array} terms An array of
|
||||
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
|
||||
* If {@code ignoreCase} is true, each search term must already be lowercase.
|
||||
* The matchWholeWordOnly value is a per-term attribute because some terms
|
||||
* may be CJK, while others are not. (For correctness, matchWholeWordOnly
|
||||
* should always be false for CJK terms.).
|
||||
* @param {Function} annotateFn {function(number, string) : string} A function
|
||||
* that takes
|
||||
* (1) the number of the term that is "hit",
|
||||
* (2) the HTML string (search term) to be annotated,
|
||||
* and returns the annotated term as an HTML string.
|
||||
* @param {*} ignoreCase Whether to ignore the case of the query terms
|
||||
* when looking for matches.
|
||||
* @return {?string} The HTML equivalent of {@code text} with terms
|
||||
* annotated, or null if the text did not contain any of the terms.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.helpAnnotateText_ = function(text, terms, annotateFn,
|
||||
ignoreCase) {
|
||||
var hit = false;
|
||||
var resultHtml = null;
|
||||
var textToSearch = ignoreCase ? text.toLowerCase() : text;
|
||||
var textLen = textToSearch.length;
|
||||
var numTerms = terms.length;
|
||||
|
||||
// Each element will be an array of hit positions for the term.
|
||||
var termHits = new Array(numTerms);
|
||||
|
||||
// First collect all the hits into allHits.
|
||||
for (var i = 0; i < numTerms; i++) {
|
||||
var term = terms[i];
|
||||
var hits = [];
|
||||
var termText = term[0];
|
||||
if (termText != '') {
|
||||
var matchWholeWordOnly = term[1];
|
||||
var termLen = termText.length;
|
||||
var pos = 0;
|
||||
// Find each hit for term t and append to termHits.
|
||||
while (pos < textLen) {
|
||||
var hitPos = textToSearch.indexOf(termText, pos);
|
||||
if (hitPos == -1) {
|
||||
break;
|
||||
} else {
|
||||
var prevCharPos = hitPos - 1;
|
||||
var nextCharPos = hitPos + termLen;
|
||||
if (!matchWholeWordOnly ||
|
||||
((prevCharPos < 0 ||
|
||||
goog.dom.annotate.NONWORD_RE_.test(
|
||||
textToSearch.charAt(prevCharPos))) &&
|
||||
(nextCharPos >= textLen ||
|
||||
goog.dom.annotate.NONWORD_RE_.test(
|
||||
textToSearch.charAt(nextCharPos))))) {
|
||||
hits.push(hitPos);
|
||||
hit = true;
|
||||
}
|
||||
pos = hitPos + termLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
termHits[i] = hits;
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
var html = [];
|
||||
var pos = 0;
|
||||
|
||||
while (true) {
|
||||
// First determine which of the n terms is the next hit.
|
||||
var termIndexOfNextHit;
|
||||
var posOfNextHit = -1;
|
||||
|
||||
for (var i = 0; i < numTerms; i++) {
|
||||
var hits = termHits[i];
|
||||
// pull off the position of the next hit of term t
|
||||
// (it's always the first in the array because we're shifting
|
||||
// hits off the front of the array as we process them)
|
||||
// this is the next candidate to consider for the next overall hit
|
||||
if (!goog.array.isEmpty(hits)) {
|
||||
var hitPos = hits[0];
|
||||
|
||||
// Discard any hits embedded in the previous hit.
|
||||
while (hitPos >= 0 && hitPos < pos) {
|
||||
hits.shift();
|
||||
hitPos = goog.array.isEmpty(hits) ? -1 : hits[0];
|
||||
}
|
||||
|
||||
if (hitPos >= 0 && (posOfNextHit < 0 || hitPos < posOfNextHit)) {
|
||||
termIndexOfNextHit = i;
|
||||
posOfNextHit = hitPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quit if there are no more hits.
|
||||
if (posOfNextHit < 0) break;
|
||||
|
||||
// Remove the next hit from our hit list.
|
||||
termHits[termIndexOfNextHit].shift();
|
||||
|
||||
// Append everything from the end of the last hit up to this one.
|
||||
html.push(goog.string.htmlEscape(text.substr(pos, posOfNextHit - pos)));
|
||||
|
||||
// Append the annotated term.
|
||||
var termLen = terms[termIndexOfNextHit][0].length;
|
||||
var termHtml = goog.string.htmlEscape(text.substr(posOfNextHit, termLen));
|
||||
html.push(annotateFn(termIndexOfNextHit, termHtml));
|
||||
|
||||
pos = posOfNextHit + termLen;
|
||||
}
|
||||
|
||||
// Append everything after the last hit.
|
||||
html.push(goog.string.htmlEscape(text.substr(pos)));
|
||||
return html.join('');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts terms to lowercase.
|
||||
*
|
||||
* @param {Array} terms An array of
|
||||
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
|
||||
* @return {Array} An array of
|
||||
* [{string} searchTerm, {boolean} matchWholeWordOnly] tuples.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.annotate.lowercaseTerms_ = function(terms) {
|
||||
var lowercaseTerms = [];
|
||||
for (var i = 0; i < terms.length; ++i) {
|
||||
var term = terms[i];
|
||||
lowercaseTerms[i] = [term[0].toLowerCase(), term[1]];
|
||||
}
|
||||
return lowercaseTerms;
|
||||
};
|
||||
@@ -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 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.isDocumentMode(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.isDocumentMode(9) ||
|
||||
goog.userAgent.GECKO && goog.userAgent.isVersion('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.isVersion('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
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the browser range interface.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author ojan@google.com (Ojan Vafai)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.AbstractRange');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.RangeEndpoint');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.TextRangeIterator');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.string.StringBuffer');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for abstract ranges. Don't call this from subclasses.
|
||||
* @constructor
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.browserrange.AbstractRange} A clone of this range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.clone = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the browser native implementation of the range. Please refrain from
|
||||
* using this function - if you find you need the range please add wrappers for
|
||||
* the functionality you need rather than just using the native range.
|
||||
* @return {Range|TextRange} The browser native range object.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getBrowserRange =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the deepest node in the tree that contains the entire range.
|
||||
* @return {Node} The deepest node that contains the entire range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getContainer =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node the range starts in.
|
||||
* @return {Node} The element or text node the range starts in.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getStartNode =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the offset into the node the range starts in.
|
||||
* @return {number} The offset into the node the range starts in. For text
|
||||
* nodes, this is an offset into the node value. For elements, this is
|
||||
* an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getStartOffset =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node the range ends in.
|
||||
* @return {Node} The element or text node the range ends in.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getEndNode =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the offset into the node the range ends in.
|
||||
* @return {number} The offset into the node the range ends in. For text
|
||||
* nodes, this is an offset into the node value. For elements, this is
|
||||
* an offset into the childNodes array.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getEndOffset =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Compares one endpoint of this range with the endpoint of another browser
|
||||
* native range object.
|
||||
* @param {Range|TextRange} range The browser native range to compare against.
|
||||
* @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range
|
||||
* to compare with.
|
||||
* @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the other
|
||||
* range to compare with.
|
||||
* @return {number} 0 if the endpoints are equal, negative if this range
|
||||
* endpoint comes before the other range endpoint, and positive otherwise.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.compareBrowserRangeEndpoints =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Tests if this range contains the given range.
|
||||
* @param {goog.dom.browserrange.AbstractRange} abstractRange The range to test.
|
||||
* @param {boolean=} opt_allowPartial If not set or false, the range must be
|
||||
* entirely contained in the selection for this function to return true.
|
||||
* @return {boolean} Whether this range contains the given range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.containsRange =
|
||||
function(abstractRange, opt_allowPartial) {
|
||||
// IE sometimes misreports the boundaries for collapsed ranges. So if the
|
||||
// other range is collapsed, make sure the whole range is contained. This is
|
||||
// logically equivalent, and works around IE's bug.
|
||||
var checkPartial = opt_allowPartial && !abstractRange.isCollapsed();
|
||||
|
||||
var range = abstractRange.getBrowserRange();
|
||||
var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
|
||||
/** {@preserveTry} */
|
||||
try {
|
||||
if (checkPartial) {
|
||||
// There are two ways to not overlap. Being before, and being after.
|
||||
// Before is represented by this.end before range.start: comparison < 0.
|
||||
// After is represented by this.start after range.end: comparison > 0.
|
||||
// The below is the negation of not overlapping.
|
||||
return this.compareBrowserRangeEndpoints(range, end, start) >= 0 &&
|
||||
this.compareBrowserRangeEndpoints(range, start, end) <= 0;
|
||||
|
||||
} else {
|
||||
// Return true if this range bounds the parameter range from both sides.
|
||||
return this.compareBrowserRangeEndpoints(range, end, end) >= 0 &&
|
||||
this.compareBrowserRangeEndpoints(range, start, start) <= 0;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!goog.userAgent.IE) {
|
||||
throw e;
|
||||
}
|
||||
// IE sometimes throws exceptions when one range is invalid, i.e. points
|
||||
// to a node that has been removed from the document. Return false in this
|
||||
// case.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests if this range contains the given node.
|
||||
* @param {Node} node The node to test.
|
||||
* @param {boolean=} opt_allowPartial If not set or false, the node must be
|
||||
* entirely contained in the selection for this function to return true.
|
||||
* @return {boolean} Whether this range contains the given node.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.containsNode = function(node,
|
||||
opt_allowPartial) {
|
||||
return this.containsRange(
|
||||
goog.dom.browserrange.createRangeFromNodeContents(node),
|
||||
opt_allowPartial);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests if the selection is collapsed - i.e. is just a caret.
|
||||
* @return {boolean} Whether the range is collapsed.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.isCollapsed =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The text content of the range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getText =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the HTML fragment this range selects. This is slow on all browsers.
|
||||
* @return {string} HTML fragment of the range, does not include context
|
||||
* containing elements.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getHtmlFragment = function() {
|
||||
var output = new goog.string.StringBuffer();
|
||||
goog.iter.forEach(this, function(node, ignore, it) {
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
output.append(goog.string.htmlEscape(node.nodeValue.substring(
|
||||
it.getStartTextOffset(), it.getEndTextOffset())));
|
||||
} else if (node.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
if (it.isEndTag()) {
|
||||
if (goog.dom.canHaveChildren(node)) {
|
||||
output.append('</' + node.tagName + '>');
|
||||
}
|
||||
} else {
|
||||
var shallow = node.cloneNode(false);
|
||||
var html = goog.dom.getOuterHtml(shallow);
|
||||
if (goog.userAgent.IE && node.tagName == goog.dom.TagName.LI) {
|
||||
// For an LI, IE just returns "<li>" with no closing tag
|
||||
output.append(html);
|
||||
} else {
|
||||
var index = html.lastIndexOf('<');
|
||||
output.append(index ? html.substr(0, index) : html);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
return output.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns valid HTML for this range. This is fast on IE, and semi-fast on
|
||||
* other browsers.
|
||||
* @return {string} Valid HTML of the range, including context containing
|
||||
* elements.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.getValidHtml =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a RangeIterator over the contents of the range. Regardless of the
|
||||
* direction of the range, the iterator will move in document order.
|
||||
* @param {boolean=} opt_keys Unused for this iterator.
|
||||
* @return {goog.dom.RangeIterator} An iterator over tags in the range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.__iterator__ = function(
|
||||
opt_keys) {
|
||||
return new goog.dom.TextRangeIterator(this.getStartNode(),
|
||||
this.getStartOffset(), this.getEndNode(), this.getEndOffset());
|
||||
};
|
||||
|
||||
|
||||
// SELECTION MODIFICATION
|
||||
|
||||
|
||||
/**
|
||||
* Set this range as the selection in its window.
|
||||
* @param {boolean=} opt_reverse Whether to select the range in reverse,
|
||||
* if possible.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.select =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Removes the contents of the range from the document. As a side effect, the
|
||||
* selection will be collapsed. The behavior of content removal is normalized
|
||||
* across browsers. For instance, IE sometimes creates extra text nodes that
|
||||
* a W3C browser does not. That behavior is corrected for.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.removeContents =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Surrounds the text range with the specified element (on Mozilla) or with a
|
||||
* clone of the specified element (on IE). Returns a reference to the
|
||||
* surrounding element if the operation was successful; returns null if the
|
||||
* operation failed.
|
||||
* @param {Element} element The element with which the selection is to be
|
||||
* surrounded.
|
||||
* @return {Element} The surrounding element (same as the argument on Mozilla,
|
||||
* but not on IE), or null if unsuccessful.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.surroundContents =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a node before (or after) the range. The range may be disrupted
|
||||
* beyond recovery because of the way this splits nodes.
|
||||
* @param {Node} node The node to insert.
|
||||
* @param {boolean} before True to insert before, false to insert after.
|
||||
* @return {Node} The node added to the document. This may be different
|
||||
* than the node parameter because on IE we have to clone it.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.insertNode =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Surrounds this range with the two given nodes. The range may be disrupted
|
||||
* beyond recovery because of the way this splits nodes.
|
||||
* @param {Element} startNode The node to insert at the start.
|
||||
* @param {Element} endNode The node to insert at the end.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.surroundWithNodes =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Collapses the range to one of its boundary points.
|
||||
* @param {boolean} toStart Whether to collapse to the start of the range.
|
||||
*/
|
||||
goog.dom.browserrange.AbstractRange.prototype.collapse =
|
||||
goog.abstractMethod;
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the browser range namespace and interface, as
|
||||
* well as several useful utility functions.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author ojan@google.com (Ojan Vafai)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*
|
||||
* @supported IE6, IE7, FF1.5+, Safari.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange');
|
||||
goog.provide('goog.dom.browserrange.Error');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.browserrange.GeckoRange');
|
||||
goog.require('goog.dom.browserrange.IeRange');
|
||||
goog.require('goog.dom.browserrange.OperaRange');
|
||||
goog.require('goog.dom.browserrange.W3cRange');
|
||||
goog.require('goog.dom.browserrange.WebKitRange');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Common error constants.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.browserrange.Error = {
|
||||
NOT_IMPLEMENTED: 'Not Implemented'
|
||||
};
|
||||
|
||||
|
||||
// NOTE(robbyw): While it would be nice to eliminate the duplicate switches
|
||||
// below, doing so uncovers bugs in the JsCompiler in which
|
||||
// necessary code is stripped out.
|
||||
|
||||
|
||||
/**
|
||||
* Static method that returns the proper type of browser range.
|
||||
* @param {Range|TextRange} range A browser range object.
|
||||
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.createRange = function(range) {
|
||||
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
|
||||
return new goog.dom.browserrange.IeRange(
|
||||
/** @type {TextRange} */ (range),
|
||||
goog.dom.getOwnerDocument(range.parentElement()));
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
return new goog.dom.browserrange.WebKitRange(
|
||||
/** @type {Range} */ (range));
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
return new goog.dom.browserrange.GeckoRange(
|
||||
/** @type {Range} */ (range));
|
||||
} else if (goog.userAgent.OPERA) {
|
||||
return new goog.dom.browserrange.OperaRange(
|
||||
/** @type {Range} */ (range));
|
||||
} else {
|
||||
// Default other browsers, including Opera, to W3c ranges.
|
||||
return new goog.dom.browserrange.W3cRange(
|
||||
/** @type {Range} */ (range));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static method that returns the proper type of browser range.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.createRangeFromNodeContents = function(node) {
|
||||
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
|
||||
return goog.dom.browserrange.IeRange.createFromNodeContents(node);
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
return goog.dom.browserrange.WebKitRange.createFromNodeContents(node);
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
return goog.dom.browserrange.GeckoRange.createFromNodeContents(node);
|
||||
} else if (goog.userAgent.OPERA) {
|
||||
return goog.dom.browserrange.OperaRange.createFromNodeContents(node);
|
||||
} else {
|
||||
// Default other browsers to W3c ranges.
|
||||
return goog.dom.browserrange.W3cRange.createFromNodeContents(node);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static method that returns the proper type of browser range.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the node to start. This is
|
||||
* either the index into the childNodes array for element startNodes or
|
||||
* the index into the character array for text startNodes.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the node to end. This is
|
||||
* either the index into the childNodes array for element endNodes or
|
||||
* the index into the character array for text endNodes.
|
||||
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.createRangeFromNodes = function(startNode, startOffset,
|
||||
endNode, endOffset) {
|
||||
if (goog.userAgent.IE && !goog.userAgent.isDocumentMode(9)) {
|
||||
return goog.dom.browserrange.IeRange.createFromNodes(startNode, startOffset,
|
||||
endNode, endOffset);
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
return goog.dom.browserrange.WebKitRange.createFromNodes(startNode,
|
||||
startOffset, endNode, endOffset);
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
return goog.dom.browserrange.GeckoRange.createFromNodes(startNode,
|
||||
startOffset, endNode, endOffset);
|
||||
} else if (goog.userAgent.OPERA) {
|
||||
return goog.dom.browserrange.OperaRange.createFromNodes(startNode,
|
||||
startOffset, endNode, endOffset);
|
||||
} else {
|
||||
// Default other browsers to W3c ranges.
|
||||
return goog.dom.browserrange.W3cRange.createFromNodes(startNode,
|
||||
startOffset, endNode, endOffset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether the given node can contain a range end point.
|
||||
* @param {Node} node The node to check.
|
||||
* @return {boolean} Whether the given node can contain a range end point.
|
||||
*/
|
||||
goog.dom.browserrange.canContainRangeEndpoint = function(node) {
|
||||
// NOTE(user, bloom): This is not complete, as divs with style -
|
||||
// 'display:inline-block' or 'position:absolute' can also not contain range
|
||||
// endpoints. A more complete check is to see if that element can be partially
|
||||
// selected (can be container) or not.
|
||||
return goog.dom.canHaveChildren(node) ||
|
||||
node.nodeType == goog.dom.NodeType.TEXT;
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Gecko specific range wrapper. Inherits most
|
||||
* functionality from W3CRange, but adds exceptions as necessary.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.GeckoRange');
|
||||
|
||||
goog.require('goog.dom.browserrange.W3cRange');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for Gecko specific browser ranges.
|
||||
* @param {Range} range The range object.
|
||||
* @constructor
|
||||
* @extends {goog.dom.browserrange.W3cRange}
|
||||
*/
|
||||
goog.dom.browserrange.GeckoRange = function(range) {
|
||||
goog.dom.browserrange.W3cRange.call(this, range);
|
||||
};
|
||||
goog.inherits(goog.dom.browserrange.GeckoRange, goog.dom.browserrange.W3cRange);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects the given node's text.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.GeckoRange} A Gecko range wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.GeckoRange.createFromNodeContents = function(node) {
|
||||
return new goog.dom.browserrange.GeckoRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects between the given nodes.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the node to start.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the node to end.
|
||||
* @return {goog.dom.browserrange.GeckoRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.GeckoRange.createFromNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
return new goog.dom.browserrange.GeckoRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
|
||||
startOffset, endNode, endOffset));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.GeckoRange.prototype.selectInternal = function(
|
||||
selection, reversed) {
|
||||
if (!reversed || this.isCollapsed()) {
|
||||
// The base implementation for select() is more robust, and works fine for
|
||||
// collapsed and forward ranges. This works around
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=773137, and is tested by
|
||||
// range_test.html's testFocusedElementDisappears.
|
||||
goog.base(this, 'selectInternal', selection, reversed);
|
||||
} else {
|
||||
// Reversed selection -- start with a caret on the end node, and extend it
|
||||
// back to the start. Unfortunately, collapse() fails when focus is
|
||||
// invalid.
|
||||
selection.collapse(this.getEndNode(), this.getEndOffset());
|
||||
selection.extend(this.getStartNode(), this.getStartOffset());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,955 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the IE browser specific range wrapper.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author ojan@google.com (Ojan Vafai)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.IeRange');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeIterator');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.RangeEndpoint');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.browserrange.AbstractRange');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for IE specific browser ranges.
|
||||
* @param {TextRange} range The range object.
|
||||
* @param {Document} doc The document the range exists in.
|
||||
* @constructor
|
||||
* @extends {goog.dom.browserrange.AbstractRange}
|
||||
*/
|
||||
goog.dom.browserrange.IeRange = function(range, doc) {
|
||||
/**
|
||||
* The browser range object this class wraps.
|
||||
* @type {TextRange}
|
||||
* @private
|
||||
*/
|
||||
this.range_ = range;
|
||||
|
||||
/**
|
||||
* The document the range exists in.
|
||||
* @type {Document}
|
||||
* @private
|
||||
*/
|
||||
this.doc_ = doc;
|
||||
};
|
||||
goog.inherits(goog.dom.browserrange.IeRange,
|
||||
goog.dom.browserrange.AbstractRange);
|
||||
|
||||
|
||||
/**
|
||||
* Logging object.
|
||||
* @type {goog.debug.Logger}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.logger_ =
|
||||
goog.debug.Logger.getLogger('goog.dom.browserrange.IeRange');
|
||||
|
||||
|
||||
/**
|
||||
* Returns a browser range spanning the given node's contents.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {TextRange} A browser range spanning the node's contents.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.getBrowserRangeForNode_ = function(node) {
|
||||
var nodeRange = goog.dom.getOwnerDocument(node).body.createTextRange();
|
||||
if (node.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
// Elements are easy.
|
||||
nodeRange.moveToElementText(node);
|
||||
// Note(user) : If there are no child nodes of the element, the
|
||||
// range.htmlText includes the element's outerHTML. The range created above
|
||||
// is not collapsed, and should be collapsed explicitly.
|
||||
// Example : node = <div></div>
|
||||
// But if the node is sth like <br>, it shouldnt be collapsed.
|
||||
if (goog.dom.browserrange.canContainRangeEndpoint(node) &&
|
||||
!node.childNodes.length) {
|
||||
nodeRange.collapse(false);
|
||||
}
|
||||
} else {
|
||||
// Text nodes are hard.
|
||||
// Compute the offset from the nearest element related position.
|
||||
var offset = 0;
|
||||
var sibling = node;
|
||||
while (sibling = sibling.previousSibling) {
|
||||
var nodeType = sibling.nodeType;
|
||||
if (nodeType == goog.dom.NodeType.TEXT) {
|
||||
offset += sibling.length;
|
||||
} else if (nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
// Move to the space after this element.
|
||||
nodeRange.moveToElementText(sibling);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sibling) {
|
||||
nodeRange.moveToElementText(node.parentNode);
|
||||
}
|
||||
|
||||
nodeRange.collapse(!sibling);
|
||||
|
||||
if (offset) {
|
||||
nodeRange.move('character', offset);
|
||||
}
|
||||
|
||||
nodeRange.moveEnd('character', node.length);
|
||||
}
|
||||
|
||||
return nodeRange;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a browser range spanning the given nodes.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the start node.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the end node.
|
||||
* @return {TextRange} A browser range spanning the node's contents.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.getBrowserRangeForNodes_ = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
// Create a range starting at the correct start position.
|
||||
var child, collapse = false;
|
||||
if (startNode.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
if (startOffset > startNode.childNodes.length) {
|
||||
goog.dom.browserrange.IeRange.logger_.severe(
|
||||
'Cannot have startOffset > startNode child count');
|
||||
}
|
||||
child = startNode.childNodes[startOffset];
|
||||
collapse = !child;
|
||||
startNode = child || startNode.lastChild || startNode;
|
||||
startOffset = 0;
|
||||
}
|
||||
var leftRange = goog.dom.browserrange.IeRange.
|
||||
getBrowserRangeForNode_(startNode);
|
||||
|
||||
// This happens only when startNode is a text node.
|
||||
if (startOffset) {
|
||||
leftRange.move('character', startOffset);
|
||||
}
|
||||
|
||||
|
||||
// The range movements in IE are still an approximation to the standard W3C
|
||||
// behavior, and IE has its trickery when it comes to htmlText and text
|
||||
// properties of the range. So we short-circuit computation whenever we can.
|
||||
if (startNode == endNode && startOffset == endOffset) {
|
||||
leftRange.collapse(true);
|
||||
return leftRange;
|
||||
}
|
||||
|
||||
// This can happen only when the startNode is an element, and there is no node
|
||||
// at the given offset. We start at the last point inside the startNode in
|
||||
// that case.
|
||||
if (collapse) {
|
||||
leftRange.collapse(false);
|
||||
}
|
||||
|
||||
// Create a range that ends at the right position.
|
||||
collapse = false;
|
||||
if (endNode.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
if (endOffset > endNode.childNodes.length) {
|
||||
goog.dom.browserrange.IeRange.logger_.severe(
|
||||
'Cannot have endOffset > endNode child count');
|
||||
}
|
||||
child = endNode.childNodes[endOffset];
|
||||
endNode = child || endNode.lastChild || endNode;
|
||||
endOffset = 0;
|
||||
collapse = !child;
|
||||
}
|
||||
var rightRange = goog.dom.browserrange.IeRange.
|
||||
getBrowserRangeForNode_(endNode);
|
||||
rightRange.collapse(!collapse);
|
||||
if (endOffset) {
|
||||
rightRange.moveEnd('character', endOffset);
|
||||
}
|
||||
|
||||
// Merge and return.
|
||||
leftRange.setEndPoint('EndToEnd', rightRange);
|
||||
return leftRange;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a range object that selects the given node's text.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.IeRange} An IE range wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.createFromNodeContents = function(node) {
|
||||
var range = new goog.dom.browserrange.IeRange(
|
||||
goog.dom.browserrange.IeRange.getBrowserRangeForNode_(node),
|
||||
goog.dom.getOwnerDocument(node));
|
||||
|
||||
if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
|
||||
range.startNode_ = range.endNode_ = range.parentNode_ = node.parentNode;
|
||||
range.startOffset_ = goog.array.indexOf(range.parentNode_.childNodes, node);
|
||||
range.endOffset_ = range.startOffset_ + 1;
|
||||
} else {
|
||||
// Note(user) : Emulate the behavior of W3CRange - Go to deepest possible
|
||||
// range containers on both edges. It seems W3CRange did this to match the
|
||||
// IE behavior, and now it is a circle. Changing W3CRange may break clients
|
||||
// in all sorts of ways.
|
||||
var tempNode, leaf = node;
|
||||
while ((tempNode = leaf.firstChild) &&
|
||||
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
|
||||
leaf = tempNode;
|
||||
}
|
||||
range.startNode_ = leaf;
|
||||
range.startOffset_ = 0;
|
||||
|
||||
leaf = node;
|
||||
while ((tempNode = leaf.lastChild) &&
|
||||
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
|
||||
leaf = tempNode;
|
||||
}
|
||||
range.endNode_ = leaf;
|
||||
range.endOffset_ = leaf.nodeType == goog.dom.NodeType.ELEMENT ?
|
||||
leaf.childNodes.length : leaf.length;
|
||||
range.parentNode_ = node;
|
||||
}
|
||||
return range;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Static method that returns the proper type of browser range.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the start node.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the end node.
|
||||
* @return {goog.dom.browserrange.AbstractRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.createFromNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
var range = new goog.dom.browserrange.IeRange(
|
||||
goog.dom.browserrange.IeRange.getBrowserRangeForNodes_(startNode,
|
||||
startOffset, endNode, endOffset),
|
||||
goog.dom.getOwnerDocument(startNode));
|
||||
range.startNode_ = startNode;
|
||||
range.startOffset_ = startOffset;
|
||||
range.endNode_ = endNode;
|
||||
range.endOffset_ = endOffset;
|
||||
return range;
|
||||
};
|
||||
|
||||
|
||||
// Even though goog.dom.TextRange does similar caching to below, keeping these
|
||||
// caches allows for better performance in the get*Offset methods.
|
||||
|
||||
|
||||
/**
|
||||
* Lazy cache of the node containing the entire selection.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.parentNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Lazy cache of the node containing the start of the selection.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.startNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Lazy cache of the node containing the end of the selection.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.endNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Lazy cache of the offset in startNode_ where this range starts.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.startOffset_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Lazy cache of the offset in endNode_ where this range ends.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.endOffset_ = -1;
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.browserrange.IeRange} A clone of this range.
|
||||
* @override
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.clone = function() {
|
||||
var range = new goog.dom.browserrange.IeRange(
|
||||
this.range_.duplicate(), this.doc_);
|
||||
range.parentNode_ = this.parentNode_;
|
||||
range.startNode_ = this.startNode_;
|
||||
range.endNode_ = this.endNode_;
|
||||
return range;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getBrowserRange = function() {
|
||||
return this.range_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clears the cached values for containers.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.clearCachedValues_ = function() {
|
||||
this.parentNode_ = this.startNode_ = this.endNode_ = null;
|
||||
this.startOffset_ = this.endOffset_ = -1;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getContainer = function() {
|
||||
if (!this.parentNode_) {
|
||||
var selectText = this.range_.text;
|
||||
|
||||
// If the selection ends with spaces, we need to remove these to get the
|
||||
// parent container of only the real contents. This is to get around IE's
|
||||
// inconsistency where it selects the spaces after a word when you double
|
||||
// click, but leaves out the spaces during execCommands.
|
||||
var range = this.range_.duplicate();
|
||||
// We can't use goog.string.trimRight, as that will remove other whitespace
|
||||
// too.
|
||||
var rightTrimmedSelectText = selectText.replace(/ +$/, '');
|
||||
var numSpacesAtEnd = selectText.length - rightTrimmedSelectText.length;
|
||||
if (numSpacesAtEnd) {
|
||||
range.moveEnd('character', -numSpacesAtEnd);
|
||||
}
|
||||
|
||||
// Get the parent node. This should be the end, but alas, it is not.
|
||||
var parent = range.parentElement();
|
||||
|
||||
var htmlText = range.htmlText;
|
||||
var htmlTextLen = goog.string.stripNewlines(htmlText).length;
|
||||
if (this.isCollapsed() && htmlTextLen > 0) {
|
||||
return (this.parentNode_ = parent);
|
||||
}
|
||||
|
||||
// Deal with selection bug where IE thinks one of the selection's children
|
||||
// is actually the selection's parent. Relies on the assumption that the
|
||||
// HTML text of the parent container is longer than the length of the
|
||||
// selection's HTML text.
|
||||
|
||||
// Also note IE will sometimes insert \r and \n whitespace, which should be
|
||||
// disregarded. Otherwise the loop may run too long and return wrong parent
|
||||
while (htmlTextLen > goog.string.stripNewlines(parent.outerHTML).length) {
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
// Deal with IE's selecting the outer tags when you double click
|
||||
// If the innerText is the same, then we just want the inner node
|
||||
while (parent.childNodes.length == 1 &&
|
||||
parent.innerText == goog.dom.browserrange.IeRange.getNodeText_(
|
||||
parent.firstChild)) {
|
||||
// A container should be an element which can have children or a text
|
||||
// node. Elements like IMG, BR, etc. can not be containers.
|
||||
if (!goog.dom.browserrange.canContainRangeEndpoint(parent.firstChild)) {
|
||||
break;
|
||||
}
|
||||
parent = parent.firstChild;
|
||||
}
|
||||
|
||||
// If the selection is empty, we may need to do extra work to position it
|
||||
// properly.
|
||||
if (selectText.length == 0) {
|
||||
parent = this.findDeepestContainer_(parent);
|
||||
}
|
||||
|
||||
this.parentNode_ = parent;
|
||||
}
|
||||
|
||||
return this.parentNode_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to find the deepest parent for this range, starting
|
||||
* the search from {@code node}, which must contain the range.
|
||||
* @param {Node} node The node to start the search from.
|
||||
* @return {Node} The deepest parent for this range.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.findDeepestContainer_ = function(node) {
|
||||
var childNodes = node.childNodes;
|
||||
for (var i = 0, len = childNodes.length; i < len; i++) {
|
||||
var child = childNodes[i];
|
||||
|
||||
if (goog.dom.browserrange.canContainRangeEndpoint(child)) {
|
||||
var childRange =
|
||||
goog.dom.browserrange.IeRange.getBrowserRangeForNode_(child);
|
||||
var start = goog.dom.RangeEndpoint.START;
|
||||
var end = goog.dom.RangeEndpoint.END;
|
||||
|
||||
// There are two types of erratic nodes where the range over node has
|
||||
// different htmlText than the node's outerHTML.
|
||||
// Case 1 - A node with magic child. In this case :
|
||||
// nodeRange.htmlText shows ('<p> </p>), while
|
||||
// node.outerHTML doesn't show the magic node (<p></p>).
|
||||
// Case 2 - Empty span. In this case :
|
||||
// node.outerHTML shows '<span></span>'
|
||||
// node.htmlText is just empty string ''.
|
||||
var isChildRangeErratic = (childRange.htmlText != child.outerHTML);
|
||||
|
||||
// Moreover the inRange comparison fails only when the
|
||||
var isNativeInRangeErratic = this.isCollapsed() && isChildRangeErratic;
|
||||
|
||||
// In case 2 mentioned above, childRange is also collapsed. So we need to
|
||||
// compare start of this range with both start and end of child range.
|
||||
var inChildRange = isNativeInRangeErratic ?
|
||||
(this.compareBrowserRangeEndpoints(childRange, start, start) >= 0 &&
|
||||
this.compareBrowserRangeEndpoints(childRange, start, end) <= 0) :
|
||||
this.range_.inRange(childRange);
|
||||
if (inChildRange) {
|
||||
return this.findDeepestContainer_(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getStartNode = function() {
|
||||
if (!this.startNode_) {
|
||||
this.startNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.START);
|
||||
if (this.isCollapsed()) {
|
||||
this.endNode_ = this.startNode_;
|
||||
}
|
||||
}
|
||||
return this.startNode_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getStartOffset = function() {
|
||||
if (this.startOffset_ < 0) {
|
||||
this.startOffset_ = this.getOffset_(goog.dom.RangeEndpoint.START);
|
||||
if (this.isCollapsed()) {
|
||||
this.endOffset_ = this.startOffset_;
|
||||
}
|
||||
}
|
||||
return this.startOffset_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getEndNode = function() {
|
||||
if (this.isCollapsed()) {
|
||||
return this.getStartNode();
|
||||
}
|
||||
if (!this.endNode_) {
|
||||
this.endNode_ = this.getEndpointNode_(goog.dom.RangeEndpoint.END);
|
||||
}
|
||||
return this.endNode_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getEndOffset = function() {
|
||||
if (this.isCollapsed()) {
|
||||
return this.getStartOffset();
|
||||
}
|
||||
if (this.endOffset_ < 0) {
|
||||
this.endOffset_ = this.getOffset_(goog.dom.RangeEndpoint.END);
|
||||
if (this.isCollapsed()) {
|
||||
this.startOffset_ = this.endOffset_;
|
||||
}
|
||||
}
|
||||
return this.endOffset_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.compareBrowserRangeEndpoints = function(
|
||||
range, thisEndpoint, otherEndpoint) {
|
||||
return this.range_.compareEndPoints(
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') +
|
||||
'To' +
|
||||
(otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'),
|
||||
range);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recurses to find the correct node for the given endpoint.
|
||||
* @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the node for.
|
||||
* @param {Node=} opt_node Optional node to start the search from.
|
||||
* @return {Node} The deepest node containing the endpoint.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.getEndpointNode_ = function(endpoint,
|
||||
opt_node) {
|
||||
|
||||
/** @type {Node} */
|
||||
var node = opt_node || this.getContainer();
|
||||
|
||||
// If we're at a leaf in the DOM, we're done.
|
||||
if (!node || !node.firstChild) {
|
||||
return node;
|
||||
}
|
||||
|
||||
var start = goog.dom.RangeEndpoint.START, end = goog.dom.RangeEndpoint.END;
|
||||
var isStartEndpoint = endpoint == start;
|
||||
|
||||
// Find the first/last child that overlaps the selection.
|
||||
// NOTE(user) : One of the children can be the magic node. This
|
||||
// node will have only nodeType property as valid and accessible. All other
|
||||
// dom related properties like ownerDocument, parentNode, nextSibling etc
|
||||
// cause error when accessed. Therefore use the for-loop on childNodes to
|
||||
// iterate.
|
||||
for (var j = 0, length = node.childNodes.length; j < length; j++) {
|
||||
var i = isStartEndpoint ? j : length - j - 1;
|
||||
var child = node.childNodes[i];
|
||||
var childRange;
|
||||
try {
|
||||
childRange = goog.dom.browserrange.createRangeFromNodeContents(child);
|
||||
} catch (e) {
|
||||
// If the child is the magic node, then the above will throw
|
||||
// error. The magic node exists only when editing using keyboard, so can
|
||||
// not add any unit test.
|
||||
continue;
|
||||
}
|
||||
var ieRange = childRange.getBrowserRange();
|
||||
|
||||
// Case 1 : Finding end points when this range is collapsed.
|
||||
// Note that in case of collapsed range, getEnd{Node,Offset} call
|
||||
// getStart{Node,Offset}.
|
||||
if (this.isCollapsed()) {
|
||||
// Handle situations where caret is not in a text node. In such cases,
|
||||
// the adjacent child won't be a valid range endpoint container.
|
||||
if (!goog.dom.browserrange.canContainRangeEndpoint(child)) {
|
||||
// The following handles a scenario like <div><BR>[caret]<BR></div>,
|
||||
// where point should be (div, 1).
|
||||
if (this.compareBrowserRangeEndpoints(ieRange, start, start) == 0) {
|
||||
this.startOffset_ = this.endOffset_ = i;
|
||||
return node;
|
||||
}
|
||||
} else if (childRange.containsRange(this)) {
|
||||
// For collapsed range, we should invert the containsRange check with
|
||||
// childRange.
|
||||
return this.getEndpointNode_(endpoint, child);
|
||||
}
|
||||
|
||||
// Case 2 - The first child encountered to have overlap this range is
|
||||
// contained entirely in this range.
|
||||
} else if (this.containsRange(childRange)) {
|
||||
// If it is an element which can not be a range endpoint container, the
|
||||
// current child offset can be used to deduce the endpoint offset.
|
||||
if (!goog.dom.browserrange.canContainRangeEndpoint(child)) {
|
||||
|
||||
// Container can't be any deeper, so current node is the container.
|
||||
if (isStartEndpoint) {
|
||||
this.startOffset_ = i;
|
||||
} else {
|
||||
this.endOffset_ = i + 1;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// If child can contain range endpoints, recurse inside this child.
|
||||
return this.getEndpointNode_(endpoint, child);
|
||||
|
||||
// Case 3 - Partial non-adjacency overlap.
|
||||
} else if (this.compareBrowserRangeEndpoints(ieRange, start, end) < 0 &&
|
||||
this.compareBrowserRangeEndpoints(ieRange, end, start) > 0) {
|
||||
// If this child overlaps the selection partially, recurse down to find
|
||||
// the first/last child the next level down that overlaps the selection
|
||||
// completely. We do not consider edge-adjacency (== 0) as overlap.
|
||||
return this.getEndpointNode_(endpoint, child);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// None of the children of this node overlapped the selection, that means
|
||||
// the selection starts/ends in this node directly.
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compares one endpoint of this range with the endpoint of a node.
|
||||
* For internal methods, we should prefer this method to containsNode.
|
||||
* containsNode has a lot of false negatives when we're dealing with
|
||||
* {@code <br>} tags.
|
||||
*
|
||||
* @param {Node} node The node to compare against.
|
||||
* @param {goog.dom.RangeEndpoint} thisEndpoint The endpoint of this range
|
||||
* to compare with.
|
||||
* @param {goog.dom.RangeEndpoint} otherEndpoint The endpoint of the node
|
||||
* to compare with.
|
||||
* @return {number} 0 if the endpoints are equal, negative if this range
|
||||
* endpoint comes before the other node endpoint, and positive otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.compareNodeEndpoints_ =
|
||||
function(node, thisEndpoint, otherEndpoint) {
|
||||
return this.range_.compareEndPoints(
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End') +
|
||||
'To' +
|
||||
(otherEndpoint == goog.dom.RangeEndpoint.START ? 'Start' : 'End'),
|
||||
goog.dom.browserrange.createRangeFromNodeContents(node).
|
||||
getBrowserRange());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the offset into the start/end container.
|
||||
* @param {goog.dom.RangeEndpoint} endpoint The endpoint to get the offset for.
|
||||
* @param {Node=} opt_container The container to get the offset relative to.
|
||||
* Defaults to the value returned by getStartNode/getEndNode.
|
||||
* @return {number} The offset.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.getOffset_ = function(endpoint,
|
||||
opt_container) {
|
||||
var isStartEndpoint = endpoint == goog.dom.RangeEndpoint.START;
|
||||
var container = opt_container ||
|
||||
(isStartEndpoint ? this.getStartNode() : this.getEndNode());
|
||||
|
||||
if (container.nodeType == goog.dom.NodeType.ELEMENT) {
|
||||
// Find the first/last child that overlaps the selection
|
||||
var children = container.childNodes;
|
||||
var len = children.length;
|
||||
var edge = isStartEndpoint ? 0 : len - 1;
|
||||
var sign = isStartEndpoint ? 1 : - 1;
|
||||
|
||||
// We find the index in the child array of the endpoint of the selection.
|
||||
for (var i = edge; i >= 0 && i < len; i += sign) {
|
||||
var child = children[i];
|
||||
// Ignore the child nodes, which could be end point containers.
|
||||
if (goog.dom.browserrange.canContainRangeEndpoint(child)) {
|
||||
continue;
|
||||
}
|
||||
// Stop looping when we reach the edge of the selection.
|
||||
var endPointCompare =
|
||||
this.compareNodeEndpoints_(child, endpoint, endpoint);
|
||||
if (endPointCompare == 0) {
|
||||
return isStartEndpoint ? i : i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// When starting from the end in an empty container, we erroneously return
|
||||
// -1: fix this to return 0.
|
||||
return i == -1 ? 0 : i;
|
||||
} else {
|
||||
// Get a temporary range object.
|
||||
var range = this.range_.duplicate();
|
||||
|
||||
// Create a range that selects the entire container.
|
||||
var nodeRange = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(
|
||||
container);
|
||||
|
||||
// Now, intersect our range with the container range - this should give us
|
||||
// the part of our selection that is in the container.
|
||||
range.setEndPoint(isStartEndpoint ? 'EndToEnd' : 'StartToStart', nodeRange);
|
||||
|
||||
var rangeLength = range.text.length;
|
||||
return isStartEndpoint ? container.length - rangeLength : rangeLength;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text of the given node. Uses IE specific properties.
|
||||
* @param {Node} node The node to retrieve the text of.
|
||||
* @return {string} The node's text.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.getNodeText_ = function(node) {
|
||||
return node.nodeType == goog.dom.NodeType.TEXT ?
|
||||
node.nodeValue : node.innerText;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether this range is valid (i.e. whether its endpoints are still in
|
||||
* the document). A range becomes invalid when, after this object was created,
|
||||
* either one or both of its endpoints are removed from the document. Use of
|
||||
* an invalid range can lead to runtime errors, particularly in IE.
|
||||
* @return {boolean} Whether the range is valid.
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.prototype.isRangeInDocument = function() {
|
||||
var range = this.doc_.body.createTextRange();
|
||||
range.moveToElementText(this.doc_.body);
|
||||
|
||||
return this.containsRange(
|
||||
new goog.dom.browserrange.IeRange(range, this.doc_), true);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.isCollapsed = function() {
|
||||
// Note(user) : The earlier implementation used (range.text == ''), but this
|
||||
// fails when (range.htmlText == '<br>')
|
||||
// Alternative: this.range_.htmlText == '';
|
||||
return this.range_.compareEndPoints('StartToEnd', this.range_) == 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getText = function() {
|
||||
return this.range_.text;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.getValidHtml = function() {
|
||||
return this.range_.htmlText;
|
||||
};
|
||||
|
||||
|
||||
// SELECTION MODIFICATION
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.select = function(opt_reverse) {
|
||||
// IE doesn't support programmatic reversed selections.
|
||||
this.range_.select();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.removeContents = function() {
|
||||
// NOTE: Sometimes htmlText is non-empty, but the range is actually empty.
|
||||
// TODO(gboyer): The htmlText check is probably unnecessary, but I left it in
|
||||
// for paranoia.
|
||||
if (!this.isCollapsed() && this.range_.htmlText) {
|
||||
// Store some before-removal state.
|
||||
var startNode = this.getStartNode();
|
||||
var endNode = this.getEndNode();
|
||||
var oldText = this.range_.text;
|
||||
|
||||
// IE sometimes deletes nodes unrelated to the selection. This trick fixes
|
||||
// that problem most of the time. Even though it looks like a no-op, it is
|
||||
// somehow changing IE's internal state such that empty unrelated nodes are
|
||||
// no longer deleted.
|
||||
var clone = this.range_.duplicate();
|
||||
clone.moveStart('character', 1);
|
||||
clone.moveStart('character', -1);
|
||||
|
||||
// However, sometimes moving the start back and forth ends up changing the
|
||||
// range.
|
||||
// TODO(gboyer): This condition used to happen for empty ranges, but (1)
|
||||
// never worked, and (2) the isCollapsed call should protect against empty
|
||||
// ranges better than before. However, this is left for paranoia.
|
||||
if (clone.text == oldText) {
|
||||
this.range_ = clone;
|
||||
}
|
||||
|
||||
// Use the browser's native deletion code.
|
||||
this.range_.text = '';
|
||||
this.clearCachedValues_();
|
||||
|
||||
// Unfortunately, when deleting a portion of a single text node, IE creates
|
||||
// an extra text node unlike other browsers which just change the text in
|
||||
// the node. We normalize for that behavior here, making IE behave like all
|
||||
// the other browsers.
|
||||
var newStartNode = this.getStartNode();
|
||||
var newStartOffset = this.getStartOffset();
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var sibling = startNode.nextSibling;
|
||||
if (startNode == endNode && startNode.parentNode &&
|
||||
startNode.nodeType == goog.dom.NodeType.TEXT &&
|
||||
sibling && sibling.nodeType == goog.dom.NodeType.TEXT) {
|
||||
startNode.nodeValue += sibling.nodeValue;
|
||||
goog.dom.removeNode(sibling);
|
||||
|
||||
// Make sure to reselect the appropriate position.
|
||||
this.range_ = goog.dom.browserrange.IeRange.getBrowserRangeForNode_(
|
||||
newStartNode);
|
||||
this.range_.move('character', newStartOffset);
|
||||
this.clearCachedValues_();
|
||||
}
|
||||
} catch (e) {
|
||||
// IE throws errors on orphaned nodes.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {TextRange} range The range to get a dom helper for.
|
||||
* @return {goog.dom.DomHelper} A dom helper for the document the range
|
||||
* resides in.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.getDomHelper_ = function(range) {
|
||||
return goog.dom.getDomHelper(range.parentElement());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pastes the given element into the given range, returning the resulting
|
||||
* element.
|
||||
* @param {TextRange} range The range to paste into.
|
||||
* @param {Element} element The node to insert a copy of.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper object for the document
|
||||
* the range resides in.
|
||||
* @return {Element} The resulting copy of element.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.pasteElement_ = function(range, element,
|
||||
opt_domHelper) {
|
||||
opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(
|
||||
range);
|
||||
|
||||
// Make sure the node has a unique id.
|
||||
var id;
|
||||
var originalId = id = element.id;
|
||||
if (!id) {
|
||||
id = element.id = goog.string.createUniqueString();
|
||||
}
|
||||
|
||||
// Insert (a clone of) the node.
|
||||
range.pasteHTML(element.outerHTML);
|
||||
|
||||
// Pasting the outerHTML of the modified element into the document creates
|
||||
// a clone of the element argument. We want to return a reference to the
|
||||
// clone, not the original. However we need to remove the temporary ID
|
||||
// first.
|
||||
element = opt_domHelper.getElement(id);
|
||||
|
||||
// If element is null here, we failed.
|
||||
if (element) {
|
||||
if (!originalId) {
|
||||
element.removeAttribute('id');
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.surroundContents = function(element) {
|
||||
// Make sure the element is detached from the document.
|
||||
goog.dom.removeNode(element);
|
||||
|
||||
// IE more or less guarantees that range.htmlText is well-formed & valid.
|
||||
element.innerHTML = this.range_.htmlText;
|
||||
element = goog.dom.browserrange.IeRange.pasteElement_(this.range_, element);
|
||||
|
||||
// If element is null here, we failed.
|
||||
if (element) {
|
||||
this.range_.moveToElementText(element);
|
||||
}
|
||||
|
||||
this.clearCachedValues_();
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal handler for inserting a node.
|
||||
* @param {TextRange} clone A clone of this range's browser range object.
|
||||
* @param {Node} node The node to insert.
|
||||
* @param {boolean} before Whether to insert the node before or after the range.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use.
|
||||
* @return {Node} The resulting copy of node.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.browserrange.IeRange.insertNode_ = function(clone, node,
|
||||
before, opt_domHelper) {
|
||||
// Get a DOM helper.
|
||||
opt_domHelper = opt_domHelper || goog.dom.browserrange.IeRange.getDomHelper_(
|
||||
clone);
|
||||
|
||||
// If it's not an element, wrap it in one.
|
||||
var isNonElement;
|
||||
if (node.nodeType != goog.dom.NodeType.ELEMENT) {
|
||||
isNonElement = true;
|
||||
node = opt_domHelper.createDom(goog.dom.TagName.DIV, null, node);
|
||||
}
|
||||
|
||||
clone.collapse(before);
|
||||
node = goog.dom.browserrange.IeRange.pasteElement_(clone,
|
||||
/** @type {Element} */ (node), opt_domHelper);
|
||||
|
||||
// If we didn't want an element, unwrap the element and return the node.
|
||||
if (isNonElement) {
|
||||
// pasteElement_() may have returned a copy of the wrapper div, and the
|
||||
// node it wraps could also be a new copy. So we must extract that new
|
||||
// node from the new wrapper.
|
||||
var newNonElement = node.firstChild;
|
||||
opt_domHelper.flattenElement(node);
|
||||
node = newNonElement;
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.insertNode = function(node, before) {
|
||||
var output = goog.dom.browserrange.IeRange.insertNode_(
|
||||
this.range_.duplicate(), node, before);
|
||||
this.clearCachedValues_();
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.surroundWithNodes = function(
|
||||
startNode, endNode) {
|
||||
var clone1 = this.range_.duplicate();
|
||||
var clone2 = this.range_.duplicate();
|
||||
goog.dom.browserrange.IeRange.insertNode_(clone1, startNode, true);
|
||||
goog.dom.browserrange.IeRange.insertNode_(clone2, endNode, false);
|
||||
|
||||
this.clearCachedValues_();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.IeRange.prototype.collapse = function(toStart) {
|
||||
this.range_.collapse(toStart);
|
||||
|
||||
if (toStart) {
|
||||
this.endNode_ = this.startNode_;
|
||||
this.endOffset_ = this.startOffset_;
|
||||
} else {
|
||||
this.startNode_ = this.endNode_;
|
||||
this.startOffset_ = this.endOffset_;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Opera specific range wrapper. Inherits most
|
||||
* functionality from W3CRange, but adds exceptions as necessary.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.OperaRange');
|
||||
|
||||
goog.require('goog.dom.browserrange.W3cRange');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for Opera specific browser ranges.
|
||||
* @param {Range} range The range object.
|
||||
* @constructor
|
||||
* @extends {goog.dom.browserrange.W3cRange}
|
||||
*/
|
||||
goog.dom.browserrange.OperaRange = function(range) {
|
||||
goog.dom.browserrange.W3cRange.call(this, range);
|
||||
};
|
||||
goog.inherits(goog.dom.browserrange.OperaRange, goog.dom.browserrange.W3cRange);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects the given node's text.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.OperaRange} A Opera range wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.OperaRange.createFromNodeContents = function(node) {
|
||||
return new goog.dom.browserrange.OperaRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects between the given nodes.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the node to start.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the node to end.
|
||||
* @return {goog.dom.browserrange.OperaRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.OperaRange.createFromNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
return new goog.dom.browserrange.OperaRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
|
||||
startOffset, endNode, endOffset));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.OperaRange.prototype.selectInternal = function(
|
||||
selection, reversed) {
|
||||
// Avoid using addRange as we have to removeAllRanges first, which
|
||||
// blurs editable fields in Opera.
|
||||
selection.collapse(this.getStartNode(), this.getStartOffset());
|
||||
if (this.getEndNode() != this.getStartNode() ||
|
||||
this.getEndOffset() != this.getStartOffset()) {
|
||||
selection.extend(this.getEndNode(), this.getEndOffset());
|
||||
}
|
||||
// This can happen if the range isn't in an editable field.
|
||||
if (selection.rangeCount == 0) {
|
||||
selection.addRange(this.range_);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,365 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the W3C spec following range wrapper.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author ojan@google.com (Ojan Vafai)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.W3cRange');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.NodeType');
|
||||
goog.require('goog.dom.RangeEndpoint');
|
||||
goog.require('goog.dom.browserrange.AbstractRange');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for W3C specific browser ranges.
|
||||
* @param {Range} range The range object.
|
||||
* @constructor
|
||||
* @extends {goog.dom.browserrange.AbstractRange}
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange = function(range) {
|
||||
this.range_ = range;
|
||||
};
|
||||
goog.inherits(goog.dom.browserrange.W3cRange,
|
||||
goog.dom.browserrange.AbstractRange);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a browser range spanning the given node's contents.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {Range} A browser range spanning the node's contents.
|
||||
* @protected
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNode = function(node) {
|
||||
var nodeRange = goog.dom.getOwnerDocument(node).createRange();
|
||||
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
nodeRange.setStart(node, 0);
|
||||
nodeRange.setEnd(node, node.length);
|
||||
} else {
|
||||
if (!goog.dom.browserrange.canContainRangeEndpoint(node)) {
|
||||
var rangeParent = node.parentNode;
|
||||
var rangeStartOffset = goog.array.indexOf(rangeParent.childNodes, node);
|
||||
nodeRange.setStart(rangeParent, rangeStartOffset);
|
||||
nodeRange.setEnd(rangeParent, rangeStartOffset + 1);
|
||||
} else {
|
||||
var tempNode, leaf = node;
|
||||
while ((tempNode = leaf.firstChild) &&
|
||||
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
|
||||
leaf = tempNode;
|
||||
}
|
||||
nodeRange.setStart(leaf, 0);
|
||||
|
||||
leaf = node;
|
||||
while ((tempNode = leaf.lastChild) &&
|
||||
goog.dom.browserrange.canContainRangeEndpoint(tempNode)) {
|
||||
leaf = tempNode;
|
||||
}
|
||||
nodeRange.setEnd(leaf, leaf.nodeType == goog.dom.NodeType.ELEMENT ?
|
||||
leaf.childNodes.length : leaf.length);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeRange;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a browser range spanning the given nodes.
|
||||
* @param {Node} startNode The node to start with - should not be a BR.
|
||||
* @param {number} startOffset The offset within the start node.
|
||||
* @param {Node} endNode The node to end with - should not be a BR.
|
||||
* @param {number} endOffset The offset within the end node.
|
||||
* @return {Range} A browser range spanning the node's contents.
|
||||
* @protected
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
// Create and return the range.
|
||||
var nodeRange = goog.dom.getOwnerDocument(startNode).createRange();
|
||||
nodeRange.setStart(startNode, startOffset);
|
||||
nodeRange.setEnd(endNode, endOffset);
|
||||
return nodeRange;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects the given node's text.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.W3cRange} A Gecko range wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.createFromNodeContents = function(node) {
|
||||
return new goog.dom.browserrange.W3cRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects between the given nodes.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the start node.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the end node.
|
||||
* @return {goog.dom.browserrange.W3cRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.createFromNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
return new goog.dom.browserrange.W3cRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
|
||||
startOffset, endNode, endOffset));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.browserrange.W3cRange} A clone of this range.
|
||||
* @override
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.prototype.clone = function() {
|
||||
return new this.constructor(this.range_.cloneRange());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getBrowserRange = function() {
|
||||
return this.range_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getContainer = function() {
|
||||
return this.range_.commonAncestorContainer;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getStartNode = function() {
|
||||
return this.range_.startContainer;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getStartOffset = function() {
|
||||
return this.range_.startOffset;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getEndNode = function() {
|
||||
return this.range_.endContainer;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getEndOffset = function() {
|
||||
return this.range_.endOffset;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.compareBrowserRangeEndpoints =
|
||||
function(range, thisEndpoint, otherEndpoint) {
|
||||
return this.range_.compareBoundaryPoints(
|
||||
otherEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
goog.global['Range'].START_TO_START :
|
||||
goog.global['Range'].START_TO_END) :
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
goog.global['Range'].END_TO_START :
|
||||
goog.global['Range'].END_TO_END),
|
||||
/** @type {Range} */ (range));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.isCollapsed = function() {
|
||||
return this.range_.collapsed;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getText = function() {
|
||||
return this.range_.toString();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.getValidHtml = function() {
|
||||
var div = goog.dom.getDomHelper(this.range_.startContainer).createDom('div');
|
||||
div.appendChild(this.range_.cloneContents());
|
||||
var result = div.innerHTML;
|
||||
|
||||
if (goog.string.startsWith(result, '<') ||
|
||||
!this.isCollapsed() && !goog.string.contains(result, '<')) {
|
||||
// We attempt to mimic IE, which returns no containing element when a
|
||||
// only text nodes are selected, does return the containing element when
|
||||
// the selection is empty, and does return the element when multiple nodes
|
||||
// are selected.
|
||||
return result;
|
||||
}
|
||||
|
||||
var container = this.getContainer();
|
||||
container = container.nodeType == goog.dom.NodeType.ELEMENT ? container :
|
||||
container.parentNode;
|
||||
|
||||
var html = goog.dom.getOuterHtml(
|
||||
/** @type {Element} */ (container.cloneNode(false)));
|
||||
return html.replace('>', '>' + result);
|
||||
};
|
||||
|
||||
|
||||
// SELECTION MODIFICATION
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.select = function(reverse) {
|
||||
var win = goog.dom.getWindow(goog.dom.getOwnerDocument(this.getStartNode()));
|
||||
this.selectInternal(win.getSelection(), reverse);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select this range.
|
||||
* @param {Selection} selection Browser selection object.
|
||||
* @param {*} reverse Whether to select this range in reverse.
|
||||
* @protected
|
||||
*/
|
||||
goog.dom.browserrange.W3cRange.prototype.selectInternal = function(selection,
|
||||
reverse) {
|
||||
// Browser-specific tricks are needed to create reversed selections
|
||||
// programatically. For this generic W3C codepath, ignore the reverse
|
||||
// parameter.
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(this.range_);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.removeContents = function() {
|
||||
var range = this.range_;
|
||||
range.extractContents();
|
||||
|
||||
if (range.startContainer.hasChildNodes()) {
|
||||
// Remove any now empty nodes surrounding the extracted contents.
|
||||
var rangeStartContainer =
|
||||
range.startContainer.childNodes[range.startOffset];
|
||||
if (rangeStartContainer) {
|
||||
var rangePrevious = rangeStartContainer.previousSibling;
|
||||
|
||||
if (goog.dom.getRawTextContent(rangeStartContainer) == '') {
|
||||
goog.dom.removeNode(rangeStartContainer);
|
||||
}
|
||||
|
||||
if (rangePrevious && goog.dom.getRawTextContent(rangePrevious) == '') {
|
||||
goog.dom.removeNode(rangePrevious);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.surroundContents = function(element) {
|
||||
this.range_.surroundContents(element);
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.insertNode = function(node, before) {
|
||||
var range = this.range_.cloneRange();
|
||||
range.collapse(before);
|
||||
range.insertNode(node);
|
||||
range.detach();
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.surroundWithNodes = function(
|
||||
startNode, endNode) {
|
||||
var win = goog.dom.getWindow(
|
||||
goog.dom.getOwnerDocument(this.getStartNode()));
|
||||
var selectionRange = goog.dom.Range.createFromWindow(win);
|
||||
if (selectionRange) {
|
||||
var sNode = selectionRange.getStartNode();
|
||||
var eNode = selectionRange.getEndNode();
|
||||
var sOffset = selectionRange.getStartOffset();
|
||||
var eOffset = selectionRange.getEndOffset();
|
||||
}
|
||||
|
||||
var clone1 = this.range_.cloneRange();
|
||||
var clone2 = this.range_.cloneRange();
|
||||
|
||||
clone1.collapse(false);
|
||||
clone2.collapse(true);
|
||||
|
||||
clone1.insertNode(endNode);
|
||||
clone2.insertNode(startNode);
|
||||
|
||||
clone1.detach();
|
||||
clone2.detach();
|
||||
|
||||
if (selectionRange) {
|
||||
// There are 4 ways that surroundWithNodes can wreck the saved
|
||||
// selection object. All of them happen when an inserted node splits
|
||||
// a text node, and one of the end points of the selection was in the
|
||||
// latter half of that text node.
|
||||
//
|
||||
// Clients of this library should use saveUsingCarets to avoid this
|
||||
// problem. Unfortunately, saveUsingCarets uses this method, so that's
|
||||
// not really an option for us. :( We just recompute the offsets.
|
||||
var isInsertedNode = function(n) {
|
||||
return n == startNode || n == endNode;
|
||||
};
|
||||
if (sNode.nodeType == goog.dom.NodeType.TEXT) {
|
||||
while (sOffset > sNode.length) {
|
||||
sOffset -= sNode.length;
|
||||
do {
|
||||
sNode = sNode.nextSibling;
|
||||
} while (isInsertedNode(sNode));
|
||||
}
|
||||
}
|
||||
|
||||
if (eNode.nodeType == goog.dom.NodeType.TEXT) {
|
||||
while (eOffset > eNode.length) {
|
||||
eOffset -= eNode.length;
|
||||
do {
|
||||
eNode = eNode.nextSibling;
|
||||
} while (isInsertedNode(eNode));
|
||||
}
|
||||
}
|
||||
|
||||
goog.dom.Range.createFromNodes(
|
||||
sNode, /** @type {number} */ (sOffset),
|
||||
eNode, /** @type {number} */ (eOffset)).select();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.W3cRange.prototype.collapse = function(toStart) {
|
||||
this.range_.collapse(toStart);
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the WebKit specific range wrapper. Inherits most
|
||||
* functionality from W3CRange, but adds exceptions as necessary.
|
||||
*
|
||||
* DO NOT USE THIS FILE DIRECTLY. Use goog.dom.Range instead.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.browserrange.WebKitRange');
|
||||
|
||||
goog.require('goog.dom.RangeEndpoint');
|
||||
goog.require('goog.dom.browserrange.W3cRange');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The constructor for WebKit specific browser ranges.
|
||||
* @param {Range} range The range object.
|
||||
* @constructor
|
||||
* @extends {goog.dom.browserrange.W3cRange}
|
||||
*/
|
||||
goog.dom.browserrange.WebKitRange = function(range) {
|
||||
goog.dom.browserrange.W3cRange.call(this, range);
|
||||
};
|
||||
goog.inherits(goog.dom.browserrange.WebKitRange,
|
||||
goog.dom.browserrange.W3cRange);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects the given node's text.
|
||||
* @param {Node} node The node to select.
|
||||
* @return {goog.dom.browserrange.WebKitRange} A WebKit range wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.WebKitRange.createFromNodeContents = function(node) {
|
||||
return new goog.dom.browserrange.WebKitRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNode(node));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a range object that selects between the given nodes.
|
||||
* @param {Node} startNode The node to start with.
|
||||
* @param {number} startOffset The offset within the start node.
|
||||
* @param {Node} endNode The node to end with.
|
||||
* @param {number} endOffset The offset within the end node.
|
||||
* @return {goog.dom.browserrange.WebKitRange} A wrapper object.
|
||||
*/
|
||||
goog.dom.browserrange.WebKitRange.createFromNodes = function(startNode,
|
||||
startOffset, endNode, endOffset) {
|
||||
return new goog.dom.browserrange.WebKitRange(
|
||||
goog.dom.browserrange.W3cRange.getBrowserRangeForNodes(startNode,
|
||||
startOffset, endNode, endOffset));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.WebKitRange.prototype.compareBrowserRangeEndpoints =
|
||||
function(range, thisEndpoint, otherEndpoint) {
|
||||
// Webkit pre-528 has some bugs where compareBoundaryPoints() doesn't work the
|
||||
// way it is supposed to, but if we reverse the sense of two comparisons,
|
||||
// it works fine.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=20738
|
||||
if (goog.userAgent.isVersion('528')) {
|
||||
return (goog.dom.browserrange.WebKitRange.superClass_.
|
||||
compareBrowserRangeEndpoints.call(
|
||||
this, range, thisEndpoint, otherEndpoint));
|
||||
}
|
||||
return this.range_.compareBoundaryPoints(
|
||||
otherEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
goog.global['Range'].START_TO_START :
|
||||
goog.global['Range'].END_TO_START) : // Sense reversed
|
||||
(thisEndpoint == goog.dom.RangeEndpoint.START ?
|
||||
goog.global['Range'].START_TO_END : // Sense reversed
|
||||
goog.global['Range'].END_TO_END),
|
||||
/** @type {Range} */ (range));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.browserrange.WebKitRange.prototype.selectInternal = function(
|
||||
selection, reversed) {
|
||||
// Unselect everything. This addresses a bug in Webkit where it sometimes
|
||||
// caches the old selection.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=20117
|
||||
selection.removeAllRanges();
|
||||
|
||||
if (reversed) {
|
||||
selection.setBaseAndExtent(this.getEndNode(), this.getEndOffset(),
|
||||
this.getStartNode(), this.getStartOffset());
|
||||
} else {
|
||||
selection.setBaseAndExtent(this.getStartNode(), this.getStartOffset(),
|
||||
this.getEndNode(), this.getEndOffset());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS-IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for adding, removing and setting classes.
|
||||
*
|
||||
* Note: these utilities are meant to operate on HTMLElements and
|
||||
* will not work on elements with differing interfaces (such as SVGElements).
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.classes');
|
||||
|
||||
goog.require('goog.array');
|
||||
|
||||
|
||||
/**
|
||||
* Sets the entire class name of an element.
|
||||
* @param {Node} element DOM node to set class of.
|
||||
* @param {string} className Class name(s) to apply to element.
|
||||
*/
|
||||
goog.dom.classes.set = function(element, className) {
|
||||
element.className = className;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets an array of class names on an element
|
||||
* @param {Node} element DOM node to get class of.
|
||||
* @return {!Array} Class names on {@code element}. Some browsers add extra
|
||||
* properties to the array. Do not depend on any of these!
|
||||
*/
|
||||
goog.dom.classes.get = function(element) {
|
||||
var className = element.className;
|
||||
// Some types of elements don't have a className in IE (e.g. iframes).
|
||||
// Furthermore, in Firefox, className is not a string when the element is
|
||||
// an SVG element.
|
||||
return goog.isString(className) && className.match(/\S+/g) || [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a class or classes to an element. Does not add multiples of class names.
|
||||
* @param {Node} element DOM node to add class to.
|
||||
* @param {...string} var_args Class names to add.
|
||||
* @return {boolean} Whether class was added (or all classes were added).
|
||||
*/
|
||||
goog.dom.classes.add = function(element, var_args) {
|
||||
var classes = goog.dom.classes.get(element);
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
var expectedCount = classes.length + args.length;
|
||||
goog.dom.classes.add_(classes, args);
|
||||
element.className = classes.join(' ');
|
||||
return classes.length == expectedCount;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a class or classes from an element.
|
||||
* @param {Node} element DOM node to remove class from.
|
||||
* @param {...string} var_args Class name(s) to remove.
|
||||
* @return {boolean} Whether all classes in {@code var_args} were found and
|
||||
* removed.
|
||||
*/
|
||||
goog.dom.classes.remove = function(element, var_args) {
|
||||
var classes = goog.dom.classes.get(element);
|
||||
var args = goog.array.slice(arguments, 1);
|
||||
var newClasses = goog.dom.classes.getDifference_(classes, args);
|
||||
element.className = newClasses.join(' ');
|
||||
return newClasses.length == classes.length - args.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper method for {@link goog.dom.classes.add} and
|
||||
* {@link goog.dom.classes.addRemove}. Adds one or more classes to the supplied
|
||||
* classes array.
|
||||
* @param {Array.<string>} classes All class names for the element, will be
|
||||
* updated to have the classes supplied in {@code args} added.
|
||||
* @param {Array.<string>} args Class names to add.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.classes.add_ = function(classes, args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (!goog.array.contains(classes, args[i])) {
|
||||
classes.push(args[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper method for {@link goog.dom.classes.remove} and
|
||||
* {@link goog.dom.classes.addRemove}. Calculates the difference of two arrays.
|
||||
* @param {!Array.<string>} arr1 First array.
|
||||
* @param {!Array.<string>} arr2 Second array.
|
||||
* @return {!Array.<string>} The first array without the elements of the second
|
||||
* array.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.classes.getDifference_ = function(arr1, arr2) {
|
||||
return goog.array.filter(arr1, function(item) {
|
||||
return !goog.array.contains(arr2, item);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Switches a class on an element from one to another without disturbing other
|
||||
* classes. If the fromClass isn't removed, the toClass won't be added.
|
||||
* @param {Node} element DOM node to swap classes on.
|
||||
* @param {string} fromClass Class to remove.
|
||||
* @param {string} toClass Class to add.
|
||||
* @return {boolean} Whether classes were switched.
|
||||
*/
|
||||
goog.dom.classes.swap = function(element, fromClass, toClass) {
|
||||
var classes = goog.dom.classes.get(element);
|
||||
|
||||
var removed = false;
|
||||
for (var i = 0; i < classes.length; i++) {
|
||||
if (classes[i] == fromClass) {
|
||||
goog.array.splice(classes, i--, 1);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
classes.push(toClass);
|
||||
element.className = classes.join(' ');
|
||||
}
|
||||
|
||||
return removed;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds zero or more classes to an element and removes zero or more as a single
|
||||
* operation. Unlike calling {@link goog.dom.classes.add} and
|
||||
* {@link goog.dom.classes.remove} separately, this is more efficient as it only
|
||||
* parses the class property once.
|
||||
*
|
||||
* If a class is in both the remove and add lists, it will be added. Thus,
|
||||
* you can use this instead of {@link goog.dom.classes.swap} when you have
|
||||
* more than two class names that you want to swap.
|
||||
*
|
||||
* @param {Node} element DOM node to swap classes on.
|
||||
* @param {?(string|Array.<string>)} classesToRemove Class or classes to
|
||||
* remove, if null no classes are removed.
|
||||
* @param {?(string|Array.<string>)} classesToAdd Class or classes to add, if
|
||||
* null no classes are added.
|
||||
*/
|
||||
goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) {
|
||||
var classes = goog.dom.classes.get(element);
|
||||
if (goog.isString(classesToRemove)) {
|
||||
goog.array.remove(classes, classesToRemove);
|
||||
} else if (goog.isArray(classesToRemove)) {
|
||||
classes = goog.dom.classes.getDifference_(classes, classesToRemove);
|
||||
}
|
||||
|
||||
if (goog.isString(classesToAdd) &&
|
||||
!goog.array.contains(classes, classesToAdd)) {
|
||||
classes.push(classesToAdd);
|
||||
} else if (goog.isArray(classesToAdd)) {
|
||||
goog.dom.classes.add_(classes, classesToAdd);
|
||||
}
|
||||
|
||||
element.className = classes.join(' ');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if an element has a class.
|
||||
* @param {Node} element DOM node to test.
|
||||
* @param {string} className Class name to test for.
|
||||
* @return {boolean} Whether element has the class.
|
||||
*/
|
||||
goog.dom.classes.has = function(element, className) {
|
||||
return goog.array.contains(goog.dom.classes.get(element), className);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds or removes a class depending on the enabled argument.
|
||||
* @param {Node} element DOM node to add or remove the class on.
|
||||
* @param {string} className Class name to add or remove.
|
||||
* @param {boolean} enabled Whether to add or remove the class (true adds,
|
||||
* false removes).
|
||||
*/
|
||||
goog.dom.classes.enable = function(element, className, enabled) {
|
||||
if (enabled) {
|
||||
goog.dom.classes.add(element, className);
|
||||
} else {
|
||||
goog.dom.classes.remove(element, className);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a class if an element has it, and adds it the element doesn't have
|
||||
* it. Won't affect other classes on the node.
|
||||
* @param {Node} element DOM node to toggle class on.
|
||||
* @param {string} className Class to toggle.
|
||||
* @return {boolean} True if class was added, false if it was removed
|
||||
* (in other words, whether element has the class after this function has
|
||||
* been called).
|
||||
*/
|
||||
goog.dom.classes.toggle = function(element, className) {
|
||||
var add = !goog.dom.classes.has(element, className);
|
||||
goog.dom.classes.enable(element, className, add);
|
||||
return add;
|
||||
};
|
||||
@@ -0,0 +1,231 @@
|
||||
// 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 Shared code for classes_test.html & classes_quirks_test.html.
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.classes_test');
|
||||
goog.setTestOnly('goog.dom.classes_test');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classes');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
|
||||
var classes = goog.dom.classes;
|
||||
|
||||
function testGet() {
|
||||
var el = document.createElement('div');
|
||||
assertArrayEquals([], goog.dom.classes.get(el));
|
||||
el.className = 'C';
|
||||
assertArrayEquals(['C'], goog.dom.classes.get(el));
|
||||
el.className = 'C D';
|
||||
assertArrayEquals(['C', 'D'], goog.dom.classes.get(el));
|
||||
el.className = 'C\nD';
|
||||
assertArrayEquals(['C', 'D'], goog.dom.classes.get(el));
|
||||
el.className = ' C ';
|
||||
assertArrayEquals(['C'], goog.dom.classes.get(el));
|
||||
}
|
||||
|
||||
function testSetAddHasRemove() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.set(el, 'SOMECLASS');
|
||||
assertTrue('Should have SOMECLASS', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.set(el, 'OTHERCLASS');
|
||||
assertTrue('Should have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertFalse('Should not have SOMECLASS', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.add(el, 'WOOCLASS');
|
||||
assertTrue('Should have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertTrue('Should have WOOCLASS', classes.has(el, 'WOOCLASS'));
|
||||
|
||||
classes.add(el, 'ACLASS', 'BCLASS', 'CCLASS');
|
||||
assertTrue('Should have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertTrue('Should have WOOCLASS', classes.has(el, 'WOOCLASS'));
|
||||
assertTrue('Should have ACLASS', classes.has(el, 'ACLASS'));
|
||||
assertTrue('Should have BCLASS', classes.has(el, 'BCLASS'));
|
||||
assertTrue('Should have CCLASS', classes.has(el, 'CCLASS'));
|
||||
|
||||
classes.remove(el, 'CCLASS');
|
||||
assertTrue('Should have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertTrue('Should have WOOCLASS', classes.has(el, 'WOOCLASS'));
|
||||
assertTrue('Should have ACLASS', classes.has(el, 'ACLASS'));
|
||||
assertTrue('Should have BCLASS', classes.has(el, 'BCLASS'));
|
||||
assertFalse('Should not have CCLASS', classes.has(el, 'CCLASS'));
|
||||
|
||||
classes.remove(el, 'ACLASS', 'BCLASS');
|
||||
assertTrue('Should have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertTrue('Should have WOOCLASS', classes.has(el, 'WOOCLASS'));
|
||||
assertFalse('Should not have ACLASS', classes.has(el, 'ACLASS'));
|
||||
assertFalse('Should not have BCLASS', classes.has(el, 'BCLASS'));
|
||||
}
|
||||
|
||||
// While support for this isn't implied in the method documentation,
|
||||
// this is a frequently used pattern.
|
||||
function testAddWithSpacesInClassName() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.add(el, 'CLASS1 CLASS2', 'CLASS3 CLASS4');
|
||||
assertTrue('Should have CLASS1', classes.has(el, 'CLASS1'));
|
||||
assertTrue('Should have CLASS2', classes.has(el, 'CLASS2'));
|
||||
assertTrue('Should have CLASS3', classes.has(el, 'CLASS3'));
|
||||
assertTrue('Should have CLASS4', classes.has(el, 'CLASS4'));
|
||||
}
|
||||
|
||||
function testSwap() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.set(el, 'SOMECLASS FIRST');
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'SOMECLASS'));
|
||||
assertFalse('Should not have second class', classes.has(el, 'second'));
|
||||
|
||||
classes.swap(el, 'FIRST', 'second');
|
||||
|
||||
assertFalse('Should not have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'SOMECLASS'));
|
||||
assertTrue('Should have second class', classes.has(el, 'second'));
|
||||
|
||||
classes.swap(el, 'second', 'FIRST');
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'SOMECLASS'));
|
||||
assertFalse('Should not have second class', classes.has(el, 'second'));
|
||||
}
|
||||
|
||||
function testEnable() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.set(el, 'SOMECLASS FIRST');
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.enable(el, 'FIRST', false);
|
||||
|
||||
assertFalse('Should not have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.enable(el, 'FIRST', true);
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
}
|
||||
|
||||
function testToggle() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.set(el, 'SOMECLASS FIRST');
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.toggle(el, 'FIRST');
|
||||
|
||||
assertFalse('Should not have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
|
||||
classes.toggle(el, 'FIRST');
|
||||
|
||||
assertTrue('Should have FIRST class', classes.has(el, 'FIRST'));
|
||||
assertTrue('Should have SOMECLASS class', classes.has(el, 'SOMECLASS'));
|
||||
}
|
||||
|
||||
function testAddNotAddingMultiples() {
|
||||
var el = goog.dom.getElement('span6');
|
||||
assertTrue(classes.add(el, 'A'));
|
||||
assertEquals('A', el.className);
|
||||
assertFalse(classes.add(el, 'A'));
|
||||
assertEquals('A', el.className);
|
||||
assertFalse(classes.add(el, 'B', 'B'));
|
||||
assertEquals('A B', el.className);
|
||||
}
|
||||
|
||||
function testAddRemoveString() {
|
||||
var el = goog.dom.getElement('span6');
|
||||
el.className = 'A';
|
||||
|
||||
goog.dom.classes.addRemove(el, 'A', 'B');
|
||||
assertEquals('B', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, null, 'C');
|
||||
assertEquals('B C', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, 'C', 'D');
|
||||
assertEquals('B D', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, 'D', null);
|
||||
assertEquals('B', el.className);
|
||||
}
|
||||
|
||||
function testAddRemoveArray() {
|
||||
var el = goog.dom.getElement('span6');
|
||||
el.className = 'A';
|
||||
|
||||
goog.dom.classes.addRemove(el, ['A'], ['B']);
|
||||
assertEquals('B', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, [], ['C']);
|
||||
assertEquals('B C', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, ['C'], ['D']);
|
||||
assertEquals('B D', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, ['D'], []);
|
||||
assertEquals('B', el.className);
|
||||
}
|
||||
|
||||
function testAddRemoveMultiple() {
|
||||
var el = goog.dom.getElement('span6');
|
||||
el.className = 'A';
|
||||
|
||||
goog.dom.classes.addRemove(el, ['A'], ['B', 'C', 'D']);
|
||||
assertEquals('B C D', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, [], ['E', 'F']);
|
||||
assertEquals('B C D E F', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, ['C', 'E'], []);
|
||||
assertEquals('B D F', el.className);
|
||||
|
||||
goog.dom.classes.addRemove(el, ['B'], ['G']);
|
||||
assertEquals('D F G', el.className);
|
||||
}
|
||||
|
||||
// While support for this isn't implied in the method documentation,
|
||||
// this is a frequently used pattern.
|
||||
function testAddRemoveWithSpacesInClassName() {
|
||||
var el = goog.dom.getElement('p1');
|
||||
classes.addRemove(el, '', 'CLASS1 CLASS2');
|
||||
assertTrue('Should have CLASS1', classes.has(el, 'CLASS1'));
|
||||
assertTrue('Should have CLASS2', classes.has(el, 'CLASS2'));
|
||||
}
|
||||
|
||||
function testHasWithNewlines() {
|
||||
var el = goog.dom.getElement('p3');
|
||||
assertTrue('Should have SOMECLASS', classes.has(el, 'SOMECLASS'));
|
||||
assertTrue('Should also have OTHERCLASS', classes.has(el, 'OTHERCLASS'));
|
||||
assertFalse('Should not have WEIRDCLASS', classes.has(el, 'WEIRDCLASS'));
|
||||
}
|
||||
|
||||
function testEmptyClassNames() {
|
||||
var el = goog.dom.getElement('span1');
|
||||
// At the very least, make sure these do not error out.
|
||||
assertFalse('Should not have an empty class', classes.has(el, ''));
|
||||
classes.add(el, '');
|
||||
classes.toggle(el, '');
|
||||
assertFalse('Should not remove an empty class', classes.remove(el, ''));
|
||||
classes.swap(el, '', 'OTHERCLASS');
|
||||
classes.swap(el, 'TEST1', '');
|
||||
classes.addRemove(el, '', '');
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
// 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 working with IE control ranges.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
* @author jparent@google.com (Julie Parent)
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.dom.ControlRange');
|
||||
goog.provide('goog.dom.ControlRangeIterator');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.AbstractMultiRange');
|
||||
goog.require('goog.dom.AbstractRange');
|
||||
goog.require('goog.dom.RangeIterator');
|
||||
goog.require('goog.dom.RangeType');
|
||||
goog.require('goog.dom.SavedRange');
|
||||
goog.require('goog.dom.TagWalkType');
|
||||
goog.require('goog.dom.TextRange');
|
||||
goog.require('goog.iter.StopIteration');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a new control selection with no properties. Do not use this
|
||||
* constructor: use one of the goog.dom.Range.createFrom* methods instead.
|
||||
* @constructor
|
||||
* @extends {goog.dom.AbstractMultiRange}
|
||||
*/
|
||||
goog.dom.ControlRange = function() {
|
||||
};
|
||||
goog.inherits(goog.dom.ControlRange, goog.dom.AbstractMultiRange);
|
||||
|
||||
|
||||
/**
|
||||
* Create a new range wrapper from the given browser range object. Do not use
|
||||
* this method directly - please use goog.dom.Range.createFrom* instead.
|
||||
* @param {Object} controlRange The browser range object.
|
||||
* @return {goog.dom.ControlRange} A range wrapper object.
|
||||
*/
|
||||
goog.dom.ControlRange.createFromBrowserRange = function(controlRange) {
|
||||
var range = new goog.dom.ControlRange();
|
||||
range.range_ = controlRange;
|
||||
return range;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a new range wrapper that selects the given element. Do not use
|
||||
* this method directly - please use goog.dom.Range.createFrom* instead.
|
||||
* @param {...Element} var_args The element(s) to select.
|
||||
* @return {goog.dom.ControlRange} A range wrapper object.
|
||||
*/
|
||||
goog.dom.ControlRange.createFromElements = function(var_args) {
|
||||
var range = goog.dom.getOwnerDocument(arguments[0]).body.createControlRange();
|
||||
for (var i = 0, len = arguments.length; i < len; i++) {
|
||||
range.addElement(arguments[i]);
|
||||
}
|
||||
return goog.dom.ControlRange.createFromBrowserRange(range);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The IE control range obejct.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.range_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Cached list of elements.
|
||||
* @type {Array.<Element>?}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.elements_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Cached sorted list of elements.
|
||||
* @type {Array.<Element>?}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.sortedElements_ = null;
|
||||
|
||||
|
||||
// Method implementations
|
||||
|
||||
|
||||
/**
|
||||
* Clear cached values.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.clearCachedValues_ = function() {
|
||||
this.elements_ = null;
|
||||
this.sortedElements_ = null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.clone = function() {
|
||||
return goog.dom.ControlRange.createFromElements.apply(this,
|
||||
this.getElements());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getType = function() {
|
||||
return goog.dom.RangeType.CONTROL;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getBrowserRangeObject = function() {
|
||||
return this.range_ || document.body.createControlRange();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.setBrowserRangeObject = function(nativeRange) {
|
||||
if (!goog.dom.AbstractRange.isNativeControlRange(nativeRange)) {
|
||||
return false;
|
||||
}
|
||||
this.range_ = nativeRange;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getTextRangeCount = function() {
|
||||
return this.range_ ? this.range_.length : 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getTextRange = function(i) {
|
||||
return goog.dom.TextRange.createFromNodeContents(this.range_.item(i));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getContainer = function() {
|
||||
return goog.dom.findCommonAncestor.apply(null, this.getElements());
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getStartNode = function() {
|
||||
return this.getSortedElements()[0];
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getStartOffset = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getEndNode = function() {
|
||||
var sorted = this.getSortedElements();
|
||||
var startsLast = /** @type {Node} */ (goog.array.peek(sorted));
|
||||
return /** @type {Node} */ (goog.array.find(sorted, function(el) {
|
||||
return goog.dom.contains(el, startsLast);
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getEndOffset = function() {
|
||||
return this.getEndNode().childNodes.length;
|
||||
};
|
||||
|
||||
|
||||
// TODO(robbyw): Figure out how to unify getElements with TextRange API.
|
||||
/**
|
||||
* @return {Array.<Element>} Array of elements in the control range.
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.getElements = function() {
|
||||
if (!this.elements_) {
|
||||
this.elements_ = [];
|
||||
if (this.range_) {
|
||||
for (var i = 0; i < this.range_.length; i++) {
|
||||
this.elements_.push(this.range_.item(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.elements_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Array.<Element>} Array of elements comprising the control range,
|
||||
* sorted by document order.
|
||||
*/
|
||||
goog.dom.ControlRange.prototype.getSortedElements = function() {
|
||||
if (!this.sortedElements_) {
|
||||
this.sortedElements_ = this.getElements().concat();
|
||||
this.sortedElements_.sort(function(a, b) {
|
||||
return a.sourceIndex - b.sourceIndex;
|
||||
});
|
||||
}
|
||||
|
||||
return this.sortedElements_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.isRangeInDocument = function() {
|
||||
var returnValue = false;
|
||||
|
||||
try {
|
||||
returnValue = goog.array.every(this.getElements(), function(element) {
|
||||
// On IE, this throws an exception when the range is detached.
|
||||
return goog.userAgent.IE ?
|
||||
!!element.parentNode :
|
||||
goog.dom.contains(element.ownerDocument.body, element);
|
||||
});
|
||||
} catch (e) {
|
||||
// IE sometimes throws Invalid Argument errors for detached elements.
|
||||
// Note: trying to return a value from the above try block can cause IE
|
||||
// to crash. It is necessary to use the local returnValue.
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.isCollapsed = function() {
|
||||
return !this.range_ || !this.range_.length;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getText = function() {
|
||||
// TODO(robbyw): What about for table selections? Should those have text?
|
||||
return '';
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getHtmlFragment = function() {
|
||||
return goog.array.map(this.getSortedElements(), goog.dom.getOuterHtml).
|
||||
join('');
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getValidHtml = function() {
|
||||
return this.getHtmlFragment();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.getPastableHtml =
|
||||
goog.dom.ControlRange.prototype.getValidHtml;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.__iterator__ = function(opt_keys) {
|
||||
return new goog.dom.ControlRangeIterator(this);
|
||||
};
|
||||
|
||||
|
||||
// RANGE ACTIONS
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.select = function() {
|
||||
if (this.range_) {
|
||||
this.range_.select();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.removeContents = function() {
|
||||
// TODO(robbyw): Test implementing with execCommand('Delete')
|
||||
if (this.range_) {
|
||||
var nodes = [];
|
||||
for (var i = 0, len = this.range_.length; i < len; i++) {
|
||||
nodes.push(this.range_.item(i));
|
||||
}
|
||||
goog.array.forEach(nodes, goog.dom.removeNode);
|
||||
|
||||
this.collapse(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.replaceContentsWithNode = function(node) {
|
||||
// Control selections have to have the node inserted before removing the
|
||||
// selection contents because a collapsed control range doesn't have start or
|
||||
// end nodes.
|
||||
var result = this.insertNode(node, true);
|
||||
|
||||
if (!this.isCollapsed()) {
|
||||
this.removeContents();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// SAVE/RESTORE
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.saveUsingDom = function() {
|
||||
return new goog.dom.DomSavedControlRange_(this);
|
||||
};
|
||||
|
||||
|
||||
// RANGE MODIFICATION
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRange.prototype.collapse = function(toAnchor) {
|
||||
// TODO(robbyw): Should this return a text range? If so, API needs to change.
|
||||
this.range_ = null;
|
||||
this.clearCachedValues_();
|
||||
};
|
||||
|
||||
|
||||
// SAVED RANGE OBJECTS
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A SavedRange implementation using DOM endpoints.
|
||||
* @param {goog.dom.ControlRange} range The range to save.
|
||||
* @constructor
|
||||
* @extends {goog.dom.SavedRange}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.DomSavedControlRange_ = function(range) {
|
||||
/**
|
||||
* The element list.
|
||||
* @type {Array.<Element>}
|
||||
* @private
|
||||
*/
|
||||
this.elements_ = range.getElements();
|
||||
};
|
||||
goog.inherits(goog.dom.DomSavedControlRange_, goog.dom.SavedRange);
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.DomSavedControlRange_.prototype.restoreInternal = function() {
|
||||
var doc = this.elements_.length ?
|
||||
goog.dom.getOwnerDocument(this.elements_[0]) : document;
|
||||
var controlRange = doc.body.createControlRange();
|
||||
for (var i = 0, len = this.elements_.length; i < len; i++) {
|
||||
controlRange.addElement(this.elements_[i]);
|
||||
}
|
||||
return goog.dom.ControlRange.createFromBrowserRange(controlRange);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.DomSavedControlRange_.prototype.disposeInternal = function() {
|
||||
goog.dom.DomSavedControlRange_.superClass_.disposeInternal.call(this);
|
||||
delete this.elements_;
|
||||
};
|
||||
|
||||
|
||||
// RANGE ITERATION
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Subclass of goog.dom.TagIterator that iterates over a DOM range. It
|
||||
* adds functions to determine the portion of each text node that is selected.
|
||||
*
|
||||
* @param {goog.dom.ControlRange?} range The range to traverse.
|
||||
* @constructor
|
||||
* @extends {goog.dom.RangeIterator}
|
||||
*/
|
||||
goog.dom.ControlRangeIterator = function(range) {
|
||||
if (range) {
|
||||
this.elements_ = range.getSortedElements();
|
||||
this.startNode_ = this.elements_.shift();
|
||||
this.endNode_ = /** @type {Node} */ (goog.array.peek(this.elements_)) ||
|
||||
this.startNode_;
|
||||
}
|
||||
|
||||
goog.dom.RangeIterator.call(this, this.startNode_, false);
|
||||
};
|
||||
goog.inherits(goog.dom.ControlRangeIterator, goog.dom.RangeIterator);
|
||||
|
||||
|
||||
/**
|
||||
* The first node in the selection.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRangeIterator.prototype.startNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The last node in the selection.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRangeIterator.prototype.endNode_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of elements left to traverse.
|
||||
* @type {Array.<Element>?}
|
||||
* @private
|
||||
*/
|
||||
goog.dom.ControlRangeIterator.prototype.elements_ = null;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.getStartTextOffset = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.getEndTextOffset = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.getStartNode = function() {
|
||||
return this.startNode_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.getEndNode = function() {
|
||||
return this.endNode_;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.isLast = function() {
|
||||
return !this.depth && !this.elements_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Move to the next position in the selection.
|
||||
* Throws {@code goog.iter.StopIteration} when it passes the end of the range.
|
||||
* @return {Node} The node at the next position.
|
||||
* @override
|
||||
*/
|
||||
goog.dom.ControlRangeIterator.prototype.next = function() {
|
||||
// Iterate over each element in the range, and all of its children.
|
||||
if (this.isLast()) {
|
||||
throw goog.iter.StopIteration;
|
||||
} else if (!this.depth) {
|
||||
var el = this.elements_.shift();
|
||||
this.setPosition(el,
|
||||
goog.dom.TagWalkType.START_TAG,
|
||||
goog.dom.TagWalkType.START_TAG);
|
||||
return el;
|
||||
}
|
||||
|
||||
// Call the super function.
|
||||
return goog.dom.ControlRangeIterator.superClass_.next.call(this);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.ControlRangeIterator.prototype.copyFrom = function(other) {
|
||||
this.elements_ = other.elements_;
|
||||
this.startNode_ = other.startNode_;
|
||||
this.endNode_ = other.endNode_;
|
||||
|
||||
goog.dom.ControlRangeIterator.superClass_.copyFrom.call(this, other);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.dom.ControlRangeIterator} An identical iterator.
|
||||
* @override
|
||||
*/
|
||||
goog.dom.ControlRangeIterator.prototype.clone = function() {
|
||||
var copy = new goog.dom.ControlRangeIterator(null);
|
||||
copy.copyFrom(this);
|
||||
return copy;
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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 Utilities for adding, removing and setting values in
|
||||
* an Element's dataset.
|
||||
* See {@link http://www.w3.org/TR/html5/Overview.html#dom-dataset}.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.dataset');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* The DOM attribute name prefix that must be present for it to be considered
|
||||
* for a dataset.
|
||||
* @type {string}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
goog.dom.dataset.PREFIX_ = 'data-';
|
||||
|
||||
|
||||
/**
|
||||
* Sets a custom data attribute on an element. The key should be
|
||||
* in camelCase format (e.g "keyName" for the "data-key-name" attribute).
|
||||
* @param {Element} element DOM node to set the custom data attribute on.
|
||||
* @param {string} key Key for the custom data attribute.
|
||||
* @param {string} value Value for the custom data attribute.
|
||||
*/
|
||||
goog.dom.dataset.set = function(element, key, value) {
|
||||
if (element.dataset) {
|
||||
element.dataset[key] = value;
|
||||
} else {
|
||||
element.setAttribute(
|
||||
goog.dom.dataset.PREFIX_ + goog.string.toSelectorCase(key),
|
||||
value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a custom data attribute from an element. The key should be
|
||||
* in camelCase format (e.g "keyName" for the "data-key-name" attribute).
|
||||
* @param {Element} element DOM node to get the custom data attribute from.
|
||||
* @param {string} key Key for the custom data attribute.
|
||||
* @return {?string} The attribute value, if it exists.
|
||||
*/
|
||||
goog.dom.dataset.get = function(element, key) {
|
||||
if (element.dataset) {
|
||||
return element.dataset[key];
|
||||
} else {
|
||||
return element.getAttribute(goog.dom.dataset.PREFIX_ +
|
||||
goog.string.toSelectorCase(key));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes a custom data attribute from an element. The key should be
|
||||
* in camelCase format (e.g "keyName" for the "data-key-name" attribute).
|
||||
* @param {Element} element DOM node to get the custom data attribute from.
|
||||
* @param {string} key Key for the custom data attribute.
|
||||
*/
|
||||
goog.dom.dataset.remove = function(element, key) {
|
||||
if (element.dataset) {
|
||||
delete element.dataset[key];
|
||||
} else {
|
||||
element.removeAttribute(goog.dom.dataset.PREFIX_ +
|
||||
goog.string.toSelectorCase(key));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether custom data attribute exists on an element. The key should be
|
||||
* in camelCase format (e.g "keyName" for the "data-key-name" attribute).
|
||||
*
|
||||
* @param {Element} element DOM node to get the custom data attribute from.
|
||||
* @param {string} key Key for the custom data attribute.
|
||||
* @return {boolean} Whether the attibute exists.
|
||||
*/
|
||||
goog.dom.dataset.has = function(element, key) {
|
||||
if (element.dataset) {
|
||||
return key in element.dataset;
|
||||
} else if (element.hasAttribute) {
|
||||
return element.hasAttribute(goog.dom.dataset.PREFIX_ +
|
||||
goog.string.toSelectorCase(key));
|
||||
} else {
|
||||
return !!(element.getAttribute(goog.dom.dataset.PREFIX_ +
|
||||
goog.string.toSelectorCase(key)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all custom data attributes as a string map. The attribute names will be
|
||||
* camel cased (e.g., data-foo-bar -> dataset['foobar']). This operation is not
|
||||
* safe for attributes having camel-cased names clashing with already existing
|
||||
* properties (e.g., data-to-string -> dataset['toString']).
|
||||
* @param {!Element} element DOM node to get the data attributes from.
|
||||
* @return {!Object} The string map containing data attributes and their
|
||||
* respective values.
|
||||
*/
|
||||
goog.dom.dataset.getAll = function(element) {
|
||||
if (element.dataset) {
|
||||
return element.dataset;
|
||||
} else {
|
||||
var dataset = {};
|
||||
var attributes = element.attributes;
|
||||
for (var i = 0; i < attributes.length; ++i) {
|
||||
var attribute = attributes[i];
|
||||
if (goog.string.startsWith(attribute.name,
|
||||
goog.dom.dataset.PREFIX_)) {
|
||||
// We use substr(5), since it's faster than replacing 'data-' with ''.
|
||||
var key = goog.string.toCamelCase(attribute.name.substr(5));
|
||||
dataset[key] = attribute.value;
|
||||
}
|
||||
}
|
||||
return dataset;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
// 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 class that can be used to listen to font size changes.
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.FontSizeMonitor');
|
||||
goog.provide('goog.dom.FontSizeMonitor.EventType');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
// TODO(arv): Move this to goog.events instead.
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class can be used to monitor changes in font size. Instances will
|
||||
* dispatch a {@code goog.dom.FontSizeMonitor.EventType.CHANGE} event.
|
||||
* Example usage:
|
||||
* <pre>
|
||||
* var fms = new goog.dom.FontSizeMonitor();
|
||||
* goog.events.listen(fms, goog.dom.FontSizeMonitor.EventType.CHANGE,
|
||||
* function(e) {
|
||||
* alert('Font size was changed');
|
||||
* });
|
||||
* </pre>
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper object that is used to
|
||||
* determine where to insert the DOM nodes used to determine when the font
|
||||
* size changes.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.dom.FontSizeMonitor = function(opt_domHelper) {
|
||||
goog.events.EventTarget.call(this);
|
||||
|
||||
var dom = opt_domHelper || goog.dom.getDomHelper();
|
||||
|
||||
/**
|
||||
* Offscreen iframe which we use to detect resize events.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.sizeElement_ = dom.createDom(
|
||||
// The size of the iframe is expressed in em, which are font size relative
|
||||
// which will cause the iframe to be resized when the font size changes.
|
||||
// The actual values are not relevant as long as we can ensure that the
|
||||
// iframe has a non zero size and is completely off screen.
|
||||
goog.userAgent.IE ? 'div' : 'iframe', {
|
||||
'style': 'position:absolute;width:9em;height:9em;top:-99em',
|
||||
'tabIndex': -1,
|
||||
'aria-hidden': 'true'
|
||||
});
|
||||
var p = dom.getDocument().body;
|
||||
p.insertBefore(this.sizeElement_, p.firstChild);
|
||||
|
||||
/**
|
||||
* The object that we listen to resize events on.
|
||||
* @type {Element|Window}
|
||||
* @private
|
||||
*/
|
||||
var resizeTarget = this.resizeTarget_ =
|
||||
goog.userAgent.IE ? this.sizeElement_ :
|
||||
goog.dom.getFrameContentWindow(
|
||||
/** @type {HTMLIFrameElement} */ (this.sizeElement_));
|
||||
|
||||
// We need to open and close the document to get Firefox 2 to work. We must
|
||||
// not do this for IE in case we are using HTTPS since accessing the document
|
||||
// on an about:blank iframe in IE using HTTPS raises a Permission Denied
|
||||
// error.
|
||||
if (goog.userAgent.GECKO) {
|
||||
var doc = resizeTarget.document;
|
||||
doc.open();
|
||||
doc.close();
|
||||
}
|
||||
|
||||
// Listen to resize event on the window inside the iframe.
|
||||
goog.events.listen(resizeTarget, goog.events.EventType.RESIZE,
|
||||
this.handleResize_, false, this);
|
||||
|
||||
/**
|
||||
* Last measured width of the iframe element.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.lastWidth_ = this.sizeElement_.offsetWidth;
|
||||
};
|
||||
goog.inherits(goog.dom.FontSizeMonitor, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The event types that the FontSizeMonitor fires.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.dom.FontSizeMonitor.EventType = {
|
||||
// TODO(arv): Change value to 'change' after updating the callers.
|
||||
CHANGE: 'fontsizechange'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Constant for the change event.
|
||||
* @type {string}
|
||||
* @deprecated Use {@code goog.dom.FontSizeMonitor.EventType.CHANGE} instead.
|
||||
*/
|
||||
goog.dom.FontSizeMonitor.CHANGE_EVENT =
|
||||
goog.dom.FontSizeMonitor.EventType.CHANGE;
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.dom.FontSizeMonitor.prototype.disposeInternal = function() {
|
||||
goog.dom.FontSizeMonitor.superClass_.disposeInternal.call(this);
|
||||
|
||||
goog.events.unlisten(this.resizeTarget_, goog.events.EventType.RESIZE,
|
||||
this.handleResize_, false, this);
|
||||
this.resizeTarget_ = null;
|
||||
|
||||
// Firefox 2 crashes if the iframe is removed during the unload phase.
|
||||
if (!goog.userAgent.GECKO || goog.userAgent.isVersion('1.9')) {
|
||||
goog.dom.removeNode(this.sizeElement_);
|
||||
}
|
||||
delete this.sizeElement_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the onresize event of the iframe and dispatches a change event in
|
||||
* case its size really changed.
|
||||
* @param {goog.events.BrowserEvent} e The event object.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.FontSizeMonitor.prototype.handleResize_ = function(e) {
|
||||
// Only dispatch the event if the size really changed. Some newer browsers do
|
||||
// not really change the font-size, instead they zoom the whole page. This
|
||||
// does trigger window resize events on the iframe but the logical pixel size
|
||||
// remains the same (the device pixel size changes but that is irrelevant).
|
||||
var currentWidth = this.sizeElement_.offsetWidth;
|
||||
if (this.lastWidth_ != currentWidth) {
|
||||
this.lastWidth_ = currentWidth;
|
||||
this.dispatchEvent(goog.dom.FontSizeMonitor.EventType.CHANGE);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,413 @@
|
||||
// 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 a form and elements.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
* @author jonp@google.com (Jon Perlow)
|
||||
* @author elsigh@google.com (Lindsey Simon)
|
||||
*/
|
||||
|
||||
goog.provide('goog.dom.forms');
|
||||
|
||||
goog.require('goog.structs.Map');
|
||||
|
||||
|
||||
/**
|
||||
* Returns form data as a map of name to value arrays. This doesn't
|
||||
* support file inputs.
|
||||
* @param {HTMLFormElement} form The form.
|
||||
* @return {!goog.structs.Map} A map of the form data as form name to arrays of
|
||||
* values.
|
||||
*/
|
||||
goog.dom.forms.getFormDataMap = function(form) {
|
||||
var map = new goog.structs.Map();
|
||||
goog.dom.forms.getFormDataHelper_(form, map,
|
||||
goog.dom.forms.addFormDataToMap_);
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the form data as an application/x-www-url-encoded string. This
|
||||
* doesn't support file inputs.
|
||||
* @param {HTMLFormElement} form The form.
|
||||
* @return {string} An application/x-www-url-encoded string.
|
||||
*/
|
||||
goog.dom.forms.getFormDataString = function(form) {
|
||||
var sb = [];
|
||||
goog.dom.forms.getFormDataHelper_(form, sb,
|
||||
goog.dom.forms.addFormDataToStringBuffer_);
|
||||
return sb.join('&');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the form data as a map or an application/x-www-url-encoded
|
||||
* string. This doesn't support file inputs.
|
||||
* @param {HTMLFormElement} form The form.
|
||||
* @param {Object} result The object form data is being put in.
|
||||
* @param {Function} fnAppend Function that takes {@code result}, an element
|
||||
* name, and an element value, and adds the name/value pair to the result
|
||||
* object.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.getFormDataHelper_ = function(form, result, fnAppend) {
|
||||
var els = form.elements;
|
||||
for (var el, i = 0; el = els[i]; i++) {
|
||||
if (// Make sure we don't include elements that are not part of the form.
|
||||
// Some browsers include non-form elements. Check for 'form' property.
|
||||
// See http://code.google.com/p/closure-library/issues/detail?id=227
|
||||
// and
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#the-input-element
|
||||
(el.form != form) ||
|
||||
el.disabled ||
|
||||
// HTMLFieldSetElement has a form property but no value.
|
||||
el.tagName.toLowerCase() == 'fieldset') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = el.name;
|
||||
switch (el.type.toLowerCase()) {
|
||||
case 'file':
|
||||
// file inputs are not supported
|
||||
case 'submit':
|
||||
case 'reset':
|
||||
case 'button':
|
||||
// don't submit these
|
||||
break;
|
||||
case 'select-multiple':
|
||||
var values = goog.dom.forms.getValue(el);
|
||||
if (values != null) {
|
||||
for (var value, j = 0; value = values[j]; j++) {
|
||||
fnAppend(result, name, value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
var value = goog.dom.forms.getValue(el);
|
||||
if (value != null) {
|
||||
fnAppend(result, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// input[type=image] are not included in the elements collection
|
||||
var inputs = form.getElementsByTagName('input');
|
||||
for (var input, i = 0; input = inputs[i]; i++) {
|
||||
if (input.form == form && input.type.toLowerCase() == 'image') {
|
||||
name = input.name;
|
||||
fnAppend(result, name, input.value);
|
||||
fnAppend(result, name + '.x', '0');
|
||||
fnAppend(result, name + '.y', '0');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds the name/value pair to the map.
|
||||
* @param {goog.structs.Map} map The map to add to.
|
||||
* @param {string} name The name.
|
||||
* @param {string} value The value.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.addFormDataToMap_ = function(map, name, value) {
|
||||
var array = map.get(name);
|
||||
if (!array) {
|
||||
array = [];
|
||||
map.set(name, array);
|
||||
}
|
||||
array.push(value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a name/value pair to an string buffer array in the form 'name=value'.
|
||||
* @param {Array} sb The string buffer array for storing data.
|
||||
* @param {string} name The name.
|
||||
* @param {string} value The value.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.addFormDataToStringBuffer_ = function(sb, name, value) {
|
||||
sb.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the form has a file input.
|
||||
* @param {HTMLFormElement} form The form.
|
||||
* @return {boolean} Whether the form has a file input.
|
||||
*/
|
||||
goog.dom.forms.hasFileInput = function(form) {
|
||||
var els = form.elements;
|
||||
for (var el, i = 0; el = els[i]; i++) {
|
||||
if (!el.disabled && el.type && el.type.toLowerCase() == 'file') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enables or disables either all elements in a form or a single form element.
|
||||
* @param {Element} el The element, either a form or an element within a form.
|
||||
* @param {boolean} disabled Whether the element should be disabled.
|
||||
*/
|
||||
goog.dom.forms.setDisabled = function(el, disabled) {
|
||||
// disable all elements in a form
|
||||
if (el.tagName == 'FORM') {
|
||||
var els = el.elements;
|
||||
for (var i = 0; el = els[i]; i++) {
|
||||
goog.dom.forms.setDisabled(el, disabled);
|
||||
}
|
||||
} else {
|
||||
// makes sure to blur buttons, multi-selects, and any elements which
|
||||
// maintain keyboard/accessibility focus when disabled
|
||||
if (disabled == true) {
|
||||
el.blur();
|
||||
}
|
||||
el.disabled = disabled;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Focuses, and optionally selects the content of, a form element.
|
||||
* @param {Element} el The form element.
|
||||
*/
|
||||
goog.dom.forms.focusAndSelect = function(el) {
|
||||
el.focus();
|
||||
if (el.select) {
|
||||
el.select();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether a form element has a value.
|
||||
* @param {Element} el The element.
|
||||
* @return {boolean} Whether the form has a value.
|
||||
*/
|
||||
goog.dom.forms.hasValue = function(el) {
|
||||
var value = goog.dom.forms.getValue(el);
|
||||
return !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether a named form field has a value.
|
||||
* @param {HTMLFormElement} form The form element.
|
||||
* @param {string} name Name of an input to the form.
|
||||
* @return {boolean} Whether the form has a value.
|
||||
*/
|
||||
goog.dom.forms.hasValueByName = function(form, name) {
|
||||
var value = goog.dom.forms.getValueByName(form, name);
|
||||
return !!value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current value of any element with a type.
|
||||
* @param {Element} el The element.
|
||||
* @return {string|Array.<string>|null} The current value of the element
|
||||
* (or null).
|
||||
*/
|
||||
goog.dom.forms.getValue = function(el) {
|
||||
var type = el.type;
|
||||
if (!goog.isDef(type)) {
|
||||
return null;
|
||||
}
|
||||
switch (type.toLowerCase()) {
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
return goog.dom.forms.getInputChecked_(el);
|
||||
case 'select-one':
|
||||
return goog.dom.forms.getSelectSingle_(el);
|
||||
case 'select-multiple':
|
||||
return goog.dom.forms.getSelectMultiple_(el);
|
||||
default:
|
||||
return goog.isDef(el.value) ? el.value : null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Alias for goog.dom.form.element.getValue
|
||||
* @type {Function}
|
||||
* @deprecated Use {@link goog.dom.forms.getValue} instead.
|
||||
*/
|
||||
goog.dom.$F = goog.dom.forms.getValue;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the named form field. In the case of radio buttons,
|
||||
* returns the value of the checked button with the given name.
|
||||
*
|
||||
* @param {HTMLFormElement} form The form element.
|
||||
* @param {string} name Name of an input to the form.
|
||||
*
|
||||
* @return {Array.<string>|string|null} The value of the form element, or
|
||||
* null if the form element does not exist or has no value.
|
||||
*/
|
||||
goog.dom.forms.getValueByName = function(form, name) {
|
||||
var els = form.elements[name];
|
||||
|
||||
if (els.type) {
|
||||
return goog.dom.forms.getValue(els);
|
||||
} else {
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var val = goog.dom.forms.getValue(els[i]);
|
||||
if (val) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current value of a checkable input element.
|
||||
* @param {Element} el The element.
|
||||
* @return {?string} The value of the form element (or null).
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.getInputChecked_ = function(el) {
|
||||
return el.checked ? el.value : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current value of a select-one element.
|
||||
* @param {Element} el The element.
|
||||
* @return {?string} The value of the form element (or null).
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.getSelectSingle_ = function(el) {
|
||||
var selectedIndex = el.selectedIndex;
|
||||
return selectedIndex >= 0 ? el.options[selectedIndex].value : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current value of a select-multiple element.
|
||||
* @param {Element} el The element.
|
||||
* @return {Array.<string>?} The value of the form element (or null).
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.getSelectMultiple_ = function(el) {
|
||||
var values = [];
|
||||
for (var option, i = 0; option = el.options[i]; i++) {
|
||||
if (option.selected) {
|
||||
values.push(option.value);
|
||||
}
|
||||
}
|
||||
return values.length ? values : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the current value of any element with a type.
|
||||
* @param {Element} el The element.
|
||||
* @param {*=} opt_value The value to give to the element, which will be coerced
|
||||
* by the browser in the default case using toString. This value should be
|
||||
* an array for setting the value of select multiple elements.
|
||||
*/
|
||||
goog.dom.forms.setValue = function(el, opt_value) {
|
||||
var type = el.type;
|
||||
if (goog.isDef(type)) {
|
||||
switch (type.toLowerCase()) {
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
goog.dom.forms.setInputChecked_(el,
|
||||
/** @type {string} */ (opt_value));
|
||||
break;
|
||||
case 'select-one':
|
||||
goog.dom.forms.setSelectSingle_(el,
|
||||
/** @type {string} */ (opt_value));
|
||||
break;
|
||||
case 'select-multiple':
|
||||
goog.dom.forms.setSelectMultiple_(el,
|
||||
/** @type {Array} */ (opt_value));
|
||||
break;
|
||||
default:
|
||||
el.value = goog.isDefAndNotNull(opt_value) ? opt_value : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets a checkable input element's checked property.
|
||||
* #TODO(user): This seems potentially unintuitive since it doesn't set
|
||||
* the value property but my hunch is that the primary use case is to check a
|
||||
* checkbox, not to reset its value property.
|
||||
* @param {Element} el The element.
|
||||
* @param {string|boolean=} opt_value The value, sets the element checked if
|
||||
* val is set.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.setInputChecked_ = function(el, opt_value) {
|
||||
el.checked = opt_value ? 'checked' : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of a select-one element.
|
||||
* @param {Element} el The element.
|
||||
* @param {string=} opt_value The value of the selected option element.
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.setSelectSingle_ = function(el, opt_value) {
|
||||
// unset any prior selections
|
||||
el.selectedIndex = -1;
|
||||
if (goog.isString(opt_value)) {
|
||||
for (var option, i = 0; option = el.options[i]; i++) {
|
||||
if (option.value == opt_value) {
|
||||
option.selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the value of a select-multiple element.
|
||||
* @param {Element} el The element.
|
||||
* @param {Array.<string>|string=} opt_value The value of the selected option
|
||||
* element(s).
|
||||
* @private
|
||||
*/
|
||||
goog.dom.forms.setSelectMultiple_ = function(el, opt_value) {
|
||||
// reset string opt_values as an array
|
||||
if (goog.isString(opt_value)) {
|
||||
opt_value = [opt_value];
|
||||
}
|
||||
for (var option, i = 0; option = el.options[i]; i++) {
|
||||
// we have to reset the other options to false for select-multiple
|
||||
option.selected = false;
|
||||
if (opt_value) {
|
||||
for (var value, j = 0; value = opt_value[j]; j++) {
|
||||
if (option.value == value) {
|
||||
option.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user