diff --git a/haskell/ReplSession.hs b/haskell/ReplSession.hs index a7aad38..2bae7bb 100644 --- a/haskell/ReplSession.hs +++ b/haskell/ReplSession.hs @@ -10,6 +10,7 @@ import System.Process import System.Directory (getDirectoryContents) import Data.List (isSuffixOf) import Control.Monad (liftM) +import Language.Haskell.Exts data ReplSession = ReplSession { replIn :: Handle, @@ -20,11 +21,33 @@ data ReplSession = ReplSession { evalInSession :: String -> ReplSession -> IO (Either String String) evalInSession cmd session@(ReplSession input out err _) = do - clearHandle out 0 + sendCommand (":{\n" ++ prepareCode cmd ++ "\n") session + clearHandle out 10 clearHandle err 0 - sendCommand (cmd ++ "\n") session + sendCommand ":}\n" session readEvalOutput session +prepareCode :: String -> String +prepareCode code = case parseCode code of + [] -> code + decls -> if isFunDecl $ head decls + then prependLet decls + else code + +isFunDecl :: Decl -> Bool +isFunDecl (TypeSig {}) = True +isFunDecl (FunBind {}) = True +isFunDecl (PatBind {}) = True +isFunDecl _ = False + +prependLet :: [Decl] -> String +prependLet = prettyPrint . letStmt + +parseCode :: String -> [Decl] +parseCode code = case parseFileContents code of + (ParseOk (Module _ _ _ _ _ _ decls)) -> decls + (ParseFailed {}) -> [] + readEvalOutput :: ReplSession -> IO (Either String String) readEvalOutput (ReplSession _ out err _) = do output <- readUntil out ("--EvalFinished\n" `isSuffixOf`) diff --git a/haskell_compiled.js b/haskell_compiled.js index 8e7a832..38c4210 100644 --- a/haskell_compiled.js +++ b/haskell_compiled.js @@ -1,5 +1,5 @@ -if(!lt.util.load.provided_QMARK_('goog.debug.Error')) { -// Copyright 2009 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.disposable.IDisposable')) { +// 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. @@ -14,45 +14,39 @@ if(!lt.util.load.provided_QMARK_('goog.debug.Error')) { // 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. - * + * @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.debug.Error'); + +goog.provide('goog.disposable.IDisposable'); /** - * Base class for custom error objects. - * @param {*=} opt_msg The message associated with the error. - * @constructor - * @extends {Error} + * 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.debug.Error = function(opt_msg) { +goog.disposable.IDisposable = function() {}; - // 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); +/** + * Disposes of the object and its resources. + * @return {void} Nothing. + */ +goog.disposable.IDisposable.prototype.dispose; -/** @override */ -goog.debug.Error.prototype.name = 'CustomError'; +/** + * @return {boolean} Whether the object has been disposed of. + */ +goog.disposable.IDisposable.prototype.isDisposed; } -if(!lt.util.load.provided_QMARK_('goog.asserts')) { -// Copyright 2008 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.Disposable')) { +// 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. @@ -67,276 +61,291 @@ if(!lt.util.load.provided_QMARK_('goog.asserts')) { // 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, goog.asserts.assert(foo) - * will restrict foo to a truthy value. - * - * The compiler has an option to disable asserts. So code like: - * - * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); - * - * will be transformed into: - * - * var x = foo(); - * - * 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. - * + * @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.asserts'); -goog.provide('goog.asserts.AssertionError'); - -goog.require('goog.debug.Error'); -goog.require('goog.string'); +goog.provide('goog.Disposable'); +/** @suppress {extraProvide} */ +goog.provide('goog.dispose'); +/** @suppress {extraProvide} */ +goog.provide('goog.disposeAll'); -/** - * @define {boolean} Whether to strip out asserts or to leave them in. - */ -goog.asserts.ENABLE_ASSERTS = goog.DEBUG; +goog.require('goog.disposable.IDisposable'); /** - * 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. + * 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 - * @extends {goog.debug.Error} + * @implements {goog.disposable.IDisposable} */ -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(); +goog.Disposable = function() { + if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { + if (goog.Disposable.INCLUDE_STACK_ON_CREATION) { + this.creationStack = new Error().stack; + } + goog.Disposable.instances_[goog.getUid(this)] = this; + } +}; + +/** + * @enum {number} Different monitoring modes for Disposable. + */ +goog.Disposable.MonitoringMode = { /** - * The message pattern used to format the error message. Error handlers can - * use this to uniquely identify the assertion. - * @type {string} + * No monitoring. */ - this.messagePattern = messagePattern; + OFF: 0, + /** + * Creating and disposing the goog.Disposable instances is monitored. All + * disposable objects need to call the {@code goog.Disposable} base + * constructor. The PERMANENT mode must be switched on before creating any + * goog.Disposable instances. + */ + PERMANENT: 1, + /** + * INTERACTIVE mode can be switched on and off on the fly without producing + * errors. It also doesn't warn if the disposable objects don't call the + * {@code goog.Disposable} base constructor. + */ + INTERACTIVE: 2 }; -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 + * @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.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 || []); -}; +goog.define('goog.Disposable.MONITORING_MODE', 0); /** - * 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. + * @define {boolean} Whether to attach creation stack to each created disposable + * instance; This is only relevant for when MonitoringMode != OFF. */ -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; -}; +goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true); /** - * 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: - * - *
- *  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.
- *  }
- * 
- * - * @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. + * Maps the unique ID of every undisposed {@code goog.Disposable} object to + * the object itself. + * @type {!Object.} + * @private */ -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)); - } -}; +goog.Disposable.instances_ = {}; /** - * 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. + * @return {!Array.} All {@code goog.Disposable} objects that + * haven't been disposed of. */ -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)); +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 /** @type {number} */ (value); + return ret; }; /** - * 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. + * Clears the registry of undisposed objects but doesn't dispose of them. */ -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); +goog.Disposable.clearUndisposedObjects = function() { + goog.Disposable.instances_ = {}; }; /** - * 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. + * Whether the object has been disposed of. + * @type {boolean} + * @private */ -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); -}; +goog.Disposable.prototype.disposed_ = false; /** - * 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. + * Callbacks to invoke when this object is disposed. + * @type {Array.} + * @private */ -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); +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_; }; /** - * 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. + * @return {boolean} Whether the object has been disposed of. + * @deprecated Use {@link #isDisposed} instead. */ -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)); +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]; + } } - 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. + * 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.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)); +goog.Disposable.prototype.registerDisposable = function(disposable) { + this.addOnDisposeCallback(goog.partial(goog.dispose, disposable)); +}; + + +/** + * Invokes a callback function when this object is disposed. Callbacks are + * invoked in the order in which they were added. + * @param {function(this:T):?} callback The callback function. + * @param {T=} opt_scope An optional scope to call the callback in. + * @template T + */ +goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { + if (!this.onDisposeCallbacks_) { + this.onDisposeCallbacks_ = []; } - return /** @type {boolean} */ (value); + this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope)); }; /** - * 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. + * 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: + *
+ *   mypackage.MyClass = function() {
+ *     goog.base(this);
+ *     // Constructor logic specific to MyClass.
+ *     ...
+ *   };
+ *   goog.inherits(mypackage.MyClass, goog.Disposable);
  *
- * @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}
+ *   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');
+ *   };
+ * 
+ * @protected */ -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)); +goog.Disposable.prototype.disposeInternal = function() { + if (this.onDisposeCallbacks_) { + while (this.onDisposeCallbacks_.length) { + this.onDisposeCallbacks_.shift()(); + } + } +}; + + +/** + * Returns True if we can verify the object is disposed. + * Calls {@code isDisposed} on the argument if it supports it. If obj + * is not an object with an isDisposed() method, return false. + * @param {*} obj The object to investigate. + * @return {boolean} True if we can verify the object is disposed. + */ +goog.Disposable.isDisposed = function(obj) { + if (obj && typeof obj.isDisposed == 'function') { + return obj.isDisposed(); + } + return false; +}; + + +/** + * Calls {@code dispose} on the argument if it supports it. If obj is not an + * object with a dispose() method, this is a no-op. + * @param {*} obj The object to dispose of. + */ +goog.dispose = function(obj) { + if (obj && typeof obj.dispose == 'function') { + obj.dispose(); } - return /** @type {!Object} */(value); }; + +/** + * 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); + } + } +}; } -if(!lt.util.load.provided_QMARK_('goog.debug.entryPointRegistry')) { -// Copyright 2010 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.events.EventId')) { +// Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -350,153 +359,202 @@ if(!lt.util.load.provided_QMARK_('goog.debug.entryPointRegistry')) { // See the License for the specific language governing permissions and // limitations under the License. +goog.provide('goog.events.EventId'); + + + /** - * @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. + * A templated class that is used when registering for events. Typical usage: + * + * /** @type {goog.events.EventId.} + * var myEventId = new goog.events.EventId( + * goog.events.getUniqueId(('someEvent')); * - * 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. + * // No need to cast or declare here since the compiler knows the correct + * // type of 'evt' (MyEventObj). + * something.listen(myEventId, function(evt) {}); + * * - * @author nicksantos@google.com (Nick Santos) + * @param {string} eventId + * @template T + * @constructor + * @struct + * @final */ +goog.events.EventId = function(eventId) { + /** @const */ this.id = eventId; +}; -goog.provide('goog.debug.EntryPointMonitor'); -goog.provide('goog.debug.entryPointRegistry'); -goog.require('goog.asserts'); +/** + * @override + */ +goog.events.EventId.prototype.toString = function() { + return this.id; +}; +} +if(!lt.util.load.provided_QMARK_('goog.events.Event')) { +// Copyright 2005 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A base class for event objects. + * + */ +goog.provide('goog.events.Event'); +goog.provide('goog.events.EventLike'); /** - * @interface + * goog.events.Event no longer depends on goog.Disposable. Keep requiring + * goog.Disposable here to not break projects which assume this dependency. + * @suppress {extraRequire} */ -goog.debug.EntryPointMonitor = function() {}; +goog.require('goog.Disposable'); +goog.require('goog.events.EventId'); /** - * Instruments a function. - * - * @param {!Function} fn A function to instrument. - * @return {!Function} The instrumented function. + * A typedef for event like objects that are dispatchable via the + * goog.events.dispatchEvent function. strings are treated as the type for a + * goog.events.Event. Objects are treated as an extension of a new + * goog.events.Event with the type property of the object being used as the type + * of the Event. + * @typedef {string|Object|goog.events.Event|goog.events.EventId} */ -goog.debug.EntryPointMonitor.prototype.wrap; +goog.events.EventLike; + /** - * 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. + * A base class for event objects, so that they can support preventDefault and + * stopPropagation. * - * @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. + * @param {string|!goog.events.EventId} type Event Type. + * @param {Object=} opt_target Reference to the object that is the target of + * this event. It has to implement the {@code EventTarget} interface + * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}. + * @constructor */ -goog.debug.EntryPointMonitor.prototype.unwrap; +goog.events.Event = function(type, opt_target) { + /** + * Event type. + * @type {string} + */ + this.type = type instanceof goog.events.EventId ? String(type) : type; + + /** + * Target of the event. + * @type {Object|undefined} + */ + this.target = opt_target; + + /** + * Object that had the listener attached. + * @type {Object|undefined} + */ + this.currentTarget = this.target; + + /** + * Whether to cancel the event in internal capture/bubble processing for IE. + * @type {boolean} + * @public + * @suppress {underscore|visibility} Technically public, but referencing this + * outside this package is strongly discouraged. + */ + this.propagationStopped_ = false; + + /** + * Whether the default action has been prevented. + * This is a property to match the W3C specification at + * {@link http://www.w3.org/TR/DOM-Level-3-Events/ + * #events-event-type-defaultPrevented}. + * Must be treated as read-only outside the class. + * @type {boolean} + */ + this.defaultPrevented = false; + + /** + * Return value for in internal capture/bubble processing for IE. + * @type {boolean} + * @public + * @suppress {underscore|visibility} Technically public, but referencing this + * outside this package is strongly discouraged. + */ + this.returnValue_ = true; +}; /** - * An array of entry point callbacks. - * @type {!Array.} - * @private + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. */ -goog.debug.entryPointRegistry.refList_ = []; +goog.events.Event.prototype.disposeInternal = function() { +}; /** - * Monitors that should wrap all the entry points. - * @type {!Array.} - * @private + * For backwards compatibility (goog.events.Event used to inherit + * goog.Disposable). + * @deprecated Events don't need to be disposed. */ -goog.debug.entryPointRegistry.monitors_ = []; +goog.events.Event.prototype.dispose = function() { +}; /** - * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. - * Checking this allows the compiler to optimize out the registrations. - * @type {boolean} - * @private + * Stops event propagation. */ -goog.debug.entryPointRegistry.monitorsMayExist_ = false; +goog.events.Event.prototype.stopPropagation = function() { + this.propagationStopped_ = true; +}; /** - * 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. + * Prevents the default action, for example a link redirecting to a url. */ -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])); - } - } +goog.events.Event.prototype.preventDefault = function() { + this.defaultPrevented = true; + this.returnValue_ = false; }; /** - * 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. + * Stops the propagation of the event. It is equivalent to + * {@code e.stopPropagation()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. */ -goog.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); +goog.events.Event.stopPropagation = function(e) { + e.stopPropagation(); }; /** - * 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. + * Prevents the default action. It is equivalent to + * {@code e.preventDefault()}, but can be used as the callback argument of + * {@link goog.events.listen} without declaring another function. + * @param {!goog.events.Event} e An event. */ -goog.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--; +goog.events.Event.preventDefault = function(e) { + e.preventDefault(); }; } -if(!lt.util.load.provided_QMARK_('goog.disposable.IDisposable')) { -// Copyright 2011 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.debug.Error')) { +// 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. @@ -511,39 +569,98 @@ if(!lt.util.load.provided_QMARK_('goog.disposable.IDisposable')) { // 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) + * @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.disposable.IDisposable'); +goog.provide('goog.debug.Error'); /** - * 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 + * Base class for custom error objects. + * @param {*=} opt_msg The message associated with the error. + * @constructor + * @extends {Error} */ -goog.disposable.IDisposable = function() {}; +goog.debug.Error = function(opt_msg) { + + // Attempt to ensure there is a stack trace. + if (Error.captureStackTrace) { + Error.captureStackTrace(this, goog.debug.Error); + } else { + var stack = new Error().stack; + if (stack) { + this.stack = stack; + } + } + + if (opt_msg) { + this.message = String(opt_msg); + } +}; +goog.inherits(goog.debug.Error, Error); + +/** @override */ +goog.debug.Error.prototype.name = 'CustomError'; +} +if(!lt.util.load.provided_QMARK_('goog.dom.NodeType')) { +// 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. /** - * Disposes of the object and its resources. - * @return {void} Nothing. + * @fileoverview Definition of goog.dom.NodeType. */ -goog.disposable.IDisposable.prototype.dispose; + +goog.provide('goog.dom.NodeType'); /** - * @return {boolean} Whether the object has been disposed of. + * Constants for the nodeType attribute in the Node interface. + * + * These constants match those specified in the Node interface. These are + * usually present on the Node object in recent browsers, but not in older + * browsers (specifically, early IEs) and thus are given here. + * + * In some browsers (early IEs), these are not defined on the Node object, + * so they are provided here. + * + * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 + * @enum {number} */ -goog.disposable.IDisposable.prototype.isDisposed; +goog.dom.NodeType = { + ELEMENT: 1, + ATTRIBUTE: 2, + TEXT: 3, + CDATA_SECTION: 4, + ENTITY_REFERENCE: 5, + ENTITY: 6, + PROCESSING_INSTRUCTION: 7, + COMMENT: 8, + DOCUMENT: 9, + DOCUMENT_TYPE: 10, + DOCUMENT_FRAGMENT: 11, + NOTATION: 12 +}; } -if(!lt.util.load.provided_QMARK_('goog.Disposable')) { -// Copyright 2005 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.asserts')) { +// 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. @@ -558,279 +675,310 @@ if(!lt.util.load.provided_QMARK_('goog.Disposable')) { // 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) + * @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, goog.asserts.assert(foo) + * will restrict foo to a truthy value. + * + * The compiler has an option to disable asserts. So code like: + * + * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); + * + * will be transformed into: + * + * var x = foo(); + * + * 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.provide('goog.Disposable'); -goog.provide('goog.dispose'); +goog.require('goog.debug.Error'); +goog.require('goog.dom.NodeType'); +goog.require('goog.string'); -goog.require('goog.disposable.IDisposable'); + +/** + * @define {boolean} Whether to strip out asserts or to leave them in. + */ +goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); /** - * 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). + * 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 - * @implements {goog.disposable.IDisposable} + * @extends {goog.debug.Error} + * @final */ -goog.Disposable = function() { - if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) { - this.creationStack = new Error().stack; - goog.Disposable.instances_[goog.getUid(this)] = this; - } -}; - +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(); -/** - * @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. + * The message pattern used to format the error message. Error handlers can + * use this to uniquely identify the assertion. + * @type {string} */ - INTERACTIVE: 2 + this.messagePattern = messagePattern; }; +goog.inherits(goog.asserts.AssertionError, goog.debug.Error); -/** - * @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; +/** @override */ +goog.asserts.AssertionError.prototype.name = 'AssertionError'; /** - * Maps the unique ID of every undisposed {@code goog.Disposable} object to - * the object itself. - * @type {!Object.} + * 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.Disposable.instances_ = {}; - - -/** - * @return {!Array.} 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)]); - } +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; } - return ret; + // 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 || []); }; /** - * Clears the registry of undisposed objects but doesn't dispose of them. + * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is + * true. + * @template T + * @param {T} condition The condition to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {T} The value of the condition. + * @throws {goog.asserts.AssertionError} When the condition evaluates to false. */ -goog.Disposable.clearUndisposedObjects = function() { - goog.Disposable.instances_ = {}; +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; }; /** - * Whether the object has been disposed of. - * @type {boolean} - * @private + * 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: + * + *
+ *  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.
+ *  }
+ * 
+ * + * @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.Disposable.prototype.disposed_ = false; +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)); + } +}; /** - * Callbacks to invoke when this object is disposed. - * @type {Array.} - * @private + * 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.Disposable.prototype.onDisposeCallbacks_; +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); +}; /** - * If monitoring the goog.Disposable instances is enabled, stores the creation - * stack trace of the Disposable instance. - * @type {string} + * 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.Disposable.prototype.creationStack; +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); +}; /** - * @return {boolean} Whether the object has been disposed of. - * @override + * 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.Disposable.prototype.isDisposed = function() { - return this.disposed_; +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); }; /** - * @return {boolean} Whether the object has been disposed of. - * @deprecated Use {@link #isDisposed} instead. + * 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.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed; +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); +}; /** - * 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 + * 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.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]; - } +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); }; /** - * 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. + * 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.Disposable.prototype.registerDisposable = function(disposable) { - this.addOnDisposeCallback(goog.partial(goog.dispose, disposable)); +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); }; /** - * Invokes a callback function when this object is disposed. Callbacks are - * invoked in the order in which they were added. - * @param {function(this:T):?} callback The callback function. - * @param {T=} opt_scope An optional scope to call the callback in. - * @template T + * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Element} The value, likely to be a DOM Element when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not a boolean. */ -goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) { - if (!this.onDisposeCallbacks_) { - this.onDisposeCallbacks_ = []; +goog.asserts.assertElement = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) || + value.nodeType != goog.dom.NodeType.ELEMENT)) { + goog.asserts.doAssertFailure_('Expected Element but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); } - this.onDisposeCallbacks_.push(goog.bind(callback, opt_scope)); + return /** @type {!Element} */ (value); }; /** - * 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: - *
- *   mypackage.MyClass = function() {
- *     goog.base(this);
- *     // Constructor logic specific to MyClass.
- *     ...
- *   };
- *   goog.inherits(mypackage.MyClass, goog.Disposable);
+ * Checks if the value is an instance of the user-defined type if
+ * goog.asserts.ENABLE_ASSERTS is true.
  *
- *   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');
- *   };
- * 
- * @protected - */ -goog.Disposable.prototype.disposeInternal = function() { - if (this.onDisposeCallbacks_) { - while (this.onDisposeCallbacks_.length) { - this.onDisposeCallbacks_.shift()(); - } - } -}; - - -/** - * Returns True if we can verify the object is disposed. - * Calls {@code isDisposed} on the argument if it supports it. If obj - * is not an object with an isDisposed() method, return false. - * @param {*} obj The object to investigate. - * @return {boolean} True if we can verify the object is disposed. - */ -goog.Disposable.isDisposed = function(obj) { - if (obj && typeof obj.isDisposed == 'function') { - return obj.isDisposed(); - } - return false; -}; - - -/** - * Calls {@code dispose} on the argument if it supports it. If obj is not an - * object with a dispose() method, this is a no-op. - * @param {*} obj The object to dispose of. + * The compiler may tighten the type returned by this function. + * + * @param {*} value The value to check. + * @param {function(new: T, ...)} type A user-defined constructor. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @throws {goog.asserts.AssertionError} When the value is not an instance of + * type. + * @return {!T} + * @template T */ -goog.dispose = function(obj) { - if (obj && typeof obj.dispose == 'function') { - obj.dispose(); +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 value; }; /** - * 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. + * Checks that no enumerable keys are present in Object.prototype. Such keys + * would break most code that use {@code for (var ... in ...)} loops. */ -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); - } +goog.asserts.assertObjectPrototypeIsIntact = function() { + for (var key in Object.prototype) { + goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); } }; } -if(!lt.util.load.provided_QMARK_('goog.events.Listener')) { -// Copyright 2005 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.debug.entryPointRegistry')) { +// 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. @@ -845,167 +993,285 @@ if(!lt.util.load.provided_QMARK_('goog.events.Listener')) { // limitations under the License. /** - * @fileoverview Listener object. - * @see ../demos/events.html + * @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.events.Listener'); +goog.provide('goog.debug.EntryPointMonitor'); +goog.provide('goog.debug.entryPointRegistry'); -goog.require('goog.events.ListenableKey'); +goog.require('goog.asserts'); /** - * Simple class that stores information about a listener - * @implements {goog.events.ListenableKey} - * @constructor + * @interface */ -goog.events.Listener = function() { - if (goog.events.Listener.ENABLE_MONITORING) { - this.creationStack = new Error().stack; - } -}; +goog.debug.EntryPointMonitor = function() {}; /** - * @define {boolean} Whether to enable the monitoring of the - * goog.events.Listener instances. Switching on the monitoring is only - * recommended for debugging because it has a significant impact on - * performance and memory usage. If switched off, the monitoring code - * compiles down to 0 bytes. + * Instruments a function. + * + * @param {!Function} fn A function to instrument. + * @return {!Function} The instrumented function. */ -goog.events.Listener.ENABLE_MONITORING = false; +goog.debug.EntryPointMonitor.prototype.wrap; /** - * Whether the listener is a function or an object that implements handleEvent. - * @type {boolean} - * @private + * 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.events.Listener.prototype.isFunctionListener_; +goog.debug.EntryPointMonitor.prototype.unwrap; /** - * Call back function or an object with a handleEvent function. - * @type {Function|Object|null} + * An array of entry point callbacks. + * @type {!Array.} + * @private */ -goog.events.Listener.prototype.listener; +goog.debug.entryPointRegistry.refList_ = []; /** - * Proxy for callback that passes through {@link goog.events#HandleEvent_} - * @type {Function} + * Monitors that should wrap all the entry points. + * @type {!Array.} + * @private */ -goog.events.Listener.prototype.proxy; +goog.debug.entryPointRegistry.monitors_ = []; /** - * Object or node that callback is listening to - * @type {Object|goog.events.Listenable|goog.events.EventTarget} + * Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + * Checking this allows the compiler to optimize out the registrations. + * @type {boolean} + * @private */ -goog.events.Listener.prototype.src; +goog.debug.entryPointRegistry.monitorsMayExist_ = false; /** - * Type of event - * @type {string} + * 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.events.Listener.prototype.type; +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])); + } + } +}; /** - * Whether the listener is being called in the capture or bubble phase - * @type {boolean} + * 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.events.Listener.prototype.capture; +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); +}; /** - * Optional object whose context to execute the listener in - * @type {Object|undefined} + * 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.events.Listener.prototype.handler; - +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--; +}; +} +if(!lt.util.load.provided_QMARK_('goog.events.Listener')) { +// 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. /** - * The key of the listener. - * @type {number} - * @override + * @fileoverview Listener object. + * @see ../demos/events.html */ -goog.events.Listener.prototype.key = 0; - -/** - * Whether the listener has been removed. - * @type {boolean} - */ -goog.events.Listener.prototype.removed = false; +goog.provide('goog.events.Listener'); +goog.require('goog.events.ListenableKey'); -/** - * Whether to remove the listener after it has been called. - * @type {boolean} - */ -goog.events.Listener.prototype.callOnce = false; /** - * If monitoring the goog.events.Listener instances is enabled, stores the - * creation stack trace of the Disposable instance. - * @type {string} - */ -goog.events.Listener.prototype.creationStack; - - -/** - * Initializes the listener. - * @param {Function|Object} listener Callback function, or an object with a - * handleEvent function. + * Simple class that stores information about a listener + * @param {!Function} listener Callback function. * @param {Function} proxy Wrapper for the listener that patches the event. - * @param {Object} src Source object for the event. + * @param {EventTarget|goog.events.Listenable} src Source object for + * the event. * @param {string} type Event type. * @param {boolean} capture Whether in capture or bubble phase. * @param {Object=} opt_handler Object in whose context to execute the callback. + * @implements {goog.events.ListenableKey} + * @constructor */ -goog.events.Listener.prototype.init = function(listener, proxy, src, type, - capture, opt_handler) { - // we do the test of the listener here so that we do not need to - // continiously do this inside handleEvent - if (goog.isFunction(listener)) { - this.isFunctionListener_ = true; - } else if (listener && listener.handleEvent && - goog.isFunction(listener.handleEvent)) { - this.isFunctionListener_ = false; - } else { - throw Error('Invalid listener argument'); +goog.events.Listener = function( + listener, proxy, src, type, capture, opt_handler) { + if (goog.events.Listener.ENABLE_MONITORING) { + this.creationStack = new Error().stack; } + /** + * Callback function. + * @type {Function} + */ this.listener = listener; + + /** + * A wrapper over the original listener. This is used solely to + * handle native browser events (it is used to simulate the capture + * phase and to patch the event object). + * @type {Function} + */ this.proxy = proxy; + + /** + * Object or node that callback is listening to + * @type {EventTarget|goog.events.Listenable} + */ this.src = src; + + /** + * The event type. + * @const {string} + */ this.type = type; + + /** + * Whether the listener is being called in the capture or bubble phase + * @const {boolean} + */ this.capture = !!capture; + + /** + * Optional object whose context to execute the listener in + * @type {Object|undefined} + */ this.handler = opt_handler; - this.callOnce = false; + + /** + * The key of the listener. + * @const {number} + * @override + */ this.key = goog.events.ListenableKey.reserveKey(); + + /** + * Whether to remove the listener after it has been called. + * @type {boolean} + */ + this.callOnce = false; + + /** + * Whether the listener has been removed. + * @type {boolean} + */ this.removed = false; }; /** - * Calls the internal listener - * @param {Object} eventObject Event object to be passed to listener. - * @return {boolean} The result of the internal listener call. + * @define {boolean} Whether to enable the monitoring of the + * goog.events.Listener instances. Switching on the monitoring is only + * recommended for debugging because it has a significant impact on + * performance and memory usage. If switched off, the monitoring code + * compiles down to 0 bytes. */ -goog.events.Listener.prototype.handleEvent = function(eventObject) { - if (this.isFunctionListener_) { - return this.listener.call(this.handler || this.src, eventObject); - } - return this.listener.handleEvent.call(this.listener, eventObject); +goog.define('goog.events.Listener.ENABLE_MONITORING', false); + + +/** + * If monitoring the goog.events.Listener instances is enabled, stores the + * creation stack trace of the Disposable instance. + * @type {string} + */ +goog.events.Listener.prototype.creationStack; + + +/** + * Marks this listener as removed. This also remove references held by + * this listener object (such as listener and event source). + */ +goog.events.Listener.prototype.markAsRemoved = function() { + this.removed = true; + this.listener = null; + this.proxy = null; + this.src = null; + this.handler = null; }; } -if(!lt.util.load.provided_QMARK_('goog.reflect')) { -// Copyright 2009 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.object')) { +// 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. @@ -1020,539 +1286,631 @@ if(!lt.util.load.provided_QMARK_('goog.reflect')) { // limitations under the License. /** - * @fileoverview Useful compiler idioms. - * + * @fileoverview Utilities for manipulating objects/maps/hashes. */ -goog.provide('goog.reflect'); +goog.provide('goog.object'); /** - * Syntax for object literal casts. - * @see http://go/jscompiler-renaming - * @see http://code.google.com/p/closure-compiler/wiki/ - * ExperimentalTypeBasedPropertyRenaming - * - * Use this if you have an object literal whose keys need to have the same names - * as the properties of some class even after they are renamed by the compiler. + * Calls a function for each element in an object/map/hash. * - * @param {!Function} type Type to cast to. - * @param {Object} object Object literal to cast. - * @return {Object} The object literal. + * @param {Object.} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.):?} f The function to call + * for every element. This function takes 3 arguments (the element, the + * index and the object) and the return value is ignored. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @template T,K,V */ -goog.reflect.object = function(type, object) { - return object; +goog.object.forEach = function(obj, f, opt_obj) { + for (var key in obj) { + f.call(opt_obj, obj[key], key, obj); + } }; /** - * To assert to the compiler that an operation is needed when it would - * otherwise be stripped. For example: - * - * // Force a layout - * goog.reflect.sinkValue(dialog.offsetHeight); - * - * @type {!Function} + * Calls a function for each element in an object/map/hash. If that call returns + * true, adds the element to a new object. + * + * @param {Object.} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.):boolean} f The function to call + * for every element. This + * function takes 3 arguments (the element, the index and the object) + * and should return a boolean. If the return value is true the + * element is added to the result object. If it is false the + * element is not included. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object.} a new object in which only elements that passed the + * test are present. + * @template T,K,V */ -goog.reflect.sinkValue = function(x) { - goog.reflect.sinkValue[' '](x); - return x; +goog.object.filter = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + if (f.call(opt_obj, obj[key], key, obj)) { + res[key] = obj[key]; + } + } + return res; }; /** - * The compiler should optimize this function away iff no one ever uses - * goog.reflect.sinkValue. + * For every element in an object/map/hash calls a function and inserts the + * result into a new object. + * + * @param {Object.} obj The object over which to iterate. + * @param {function(this:T,V,?,Object.):R} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the object) + * and should return something. The result will be inserted + * into a new object. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {!Object.} a new object with the results from f. + * @template T,K,V,R */ -goog.reflect.sinkValue[' '] = goog.nullFunction; +goog.object.map = function(obj, f, opt_obj) { + var res = {}; + for (var key in obj) { + res[key] = f.call(opt_obj, obj[key], key, obj); + } + return res; +}; /** - * Check if a property can be accessed without throwing an exception. - * @param {Object} obj The owner of the property. - * @param {string} prop The property name. - * @return {boolean} Whether the property is accessible. Will also return true - * if obj is null. + * Calls a function for each element in an object/map/hash. If any + * call returns true, returns true (without checking the rest). If + * all calls return false, returns false. + * + * @param {Object.} obj The object to check. + * @param {function(this:T,V,?,Object.):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the element, the index and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} true if any element passes the test. + * @template T,K,V */ -goog.reflect.canAccessProperty = function(obj, prop) { - /** @preserveTry */ - try { - goog.reflect.sinkValue(obj[prop]); - return true; - } catch (e) {} +goog.object.some = function(obj, f, opt_obj) { + for (var key in obj) { + if (f.call(opt_obj, obj[key], key, obj)) { + return true; + } + } return false; }; -} -if(!lt.util.load.provided_QMARK_('goog.events.EventLike')) { -// Copyright 2005 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview A base class for event objects. + * Calls a function for each element in an object/map/hash. If + * all calls return true, returns true. If any call returns false, returns + * false at this point and does not continue to check the remaining elements. * + * @param {Object.} obj The object to check. + * @param {?function(this:T,V,?,Object.):boolean} f The function to + * call for every element. This function + * takes 3 arguments (the element, the index and the object) and should + * return a boolean. + * @param {T=} opt_obj This is used as the 'this' object within f. + * @return {boolean} false if any element fails the test. + * @template T,K,V */ +goog.object.every = function(obj, f, opt_obj) { + for (var key in obj) { + if (!f.call(opt_obj, obj[key], key, obj)) { + return false; + } + } + return true; +}; -goog.provide('goog.events.Event'); -goog.provide('goog.events.EventLike'); - -// goog.events.Event no longer depends on goog.Disposable. Keep requiring -// goog.Disposable here to not break projects which assume this dependency. -goog.require('goog.Disposable'); +/** + * Returns the number of key-value pairs in the object map. + * + * @param {Object} obj The object for which to get the number of key-value + * pairs. + * @return {number} The number of key-value pairs in the object map. + */ +goog.object.getCount = function(obj) { + // JS1.5 has __count__ but it has been deprecated so it raises a warning... + // in other words do not use. Also __count__ only includes the fields on the + // actual object and not in the prototype chain. + var rv = 0; + for (var key in obj) { + rv++; + } + return rv; +}; /** - * A typedef for event like objects that are dispatchable via the - * goog.events.dispatchEvent function. strings are treated as the type for a - * goog.events.Event. Objects are treated as an extension of a new - * goog.events.Event with the type property of the object being used as the type - * of the Event. - * @typedef {string|Object|goog.events.Event} + * Returns one key from the object map, if any exists. + * For map literals the returned key will be the first one in most of the + * browsers (a know exception is Konqueror). + * + * @param {Object} obj The object to pick a key from. + * @return {string|undefined} The key or undefined if the object is empty. */ -goog.events.EventLike; - +goog.object.getAnyKey = function(obj) { + for (var key in obj) { + return key; + } +}; /** - * A base class for event objects, so that they can support preventDefault and - * stopPropagation. + * Returns one value from the object map, if any exists. + * For map literals the returned value will be the first one in most of the + * browsers (a know exception is Konqueror). * - * @param {string} type Event Type. - * @param {Object=} opt_target Reference to the object that is the target of - * this event. It has to implement the {@code EventTarget} interface - * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}. - * @constructor + * @param {Object.} obj The object to pick a value from. + * @return {V|undefined} The value or undefined if the object is empty. + * @template K,V */ -goog.events.Event = function(type, opt_target) { - /** - * Event type. - * @type {string} - */ - this.type = type; - - /** - * Target of the event. - * @type {Object|undefined} - */ - this.target = opt_target; - - /** - * Object that had the listener attached. - * @type {Object|undefined} - */ - this.currentTarget = this.target; +goog.object.getAnyValue = function(obj) { + for (var key in obj) { + return obj[key]; + } }; /** - * For backwards compatibility (goog.events.Event used to inherit - * goog.Disposable). - * @deprecated Events don't need to be disposed. + * Whether the object/hash/map contains the given object as a value. + * An alias for goog.object.containsValue(obj, val). + * + * @param {Object.} obj The object in which to look for val. + * @param {V} val The object for which to check. + * @return {boolean} true if val is present. + * @template K,V */ -goog.events.Event.prototype.disposeInternal = function() { +goog.object.contains = function(obj, val) { + return goog.object.containsValue(obj, val); }; /** - * For backwards compatibility (goog.events.Event used to inherit - * goog.Disposable). - * @deprecated Events don't need to be disposed. + * Returns the values of the object/map/hash. + * + * @param {Object.} obj The object from which to get the values. + * @return {!Array.} The values in the object/map/hash. + * @template K,V */ -goog.events.Event.prototype.dispose = function() { +goog.object.getValues = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = obj[key]; + } + return res; }; /** - * Whether to cancel the event in internal capture/bubble processing for IE. - * @type {boolean} - * @suppress {underscore} Technically public, but referencing this outside - * this package is strongly discouraged. + * Returns the keys of the object/map/hash. + * + * @param {Object} obj The object from which to get the keys. + * @return {!Array.} Array of property keys. */ -goog.events.Event.prototype.propagationStopped_ = false; +goog.object.getKeys = function(obj) { + var res = []; + var i = 0; + for (var key in obj) { + res[i++] = key; + } + return res; +}; /** - * Whether the default action has been prevented. - * This is a property to match the W3C specification at {@link - * http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-defaultPrevented}. - * Must be treated as read-only outside the class. - * @type {boolean} + * Get a value from an object multiple levels deep. This is useful for + * pulling values from deeply nested objects, such as JSON responses. + * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3) + * + * @param {!Object} obj An object to get the value from. Can be array-like. + * @param {...(string|number|!Array.)} var_args A number of keys + * (as strings, or numbers, for array-like objects). Can also be + * specified as a single array of keys. + * @return {*} The resulting value. If, at any point, the value for a key + * is undefined, returns undefined. */ -goog.events.Event.prototype.defaultPrevented = false; +goog.object.getValueByKeys = function(obj, var_args) { + var isArrayLike = goog.isArrayLike(var_args); + var keys = isArrayLike ? var_args : arguments; + // Start with the 2nd parameter for the variable parameters syntax. + for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { + obj = obj[keys[i]]; + if (!goog.isDef(obj)) { + break; + } + } -/** - * Return value for in internal capture/bubble processing for IE. - * @type {boolean} - * @suppress {underscore} Technically public, but referencing this outside - * this package is strongly discouraged. - */ -goog.events.Event.prototype.returnValue_ = true; + return obj; +}; /** - * Stops event propagation. + * Whether the object/map/hash contains the given key. + * + * @param {Object} obj The object in which to look for key. + * @param {*} key The key for which to check. + * @return {boolean} true If the map contains the key. */ -goog.events.Event.prototype.stopPropagation = function() { - this.propagationStopped_ = true; +goog.object.containsKey = function(obj, key) { + return key in obj; }; /** - * Prevents the default action, for example a link redirecting to a url. + * Whether the object/map/hash contains the given value. This is O(n). + * + * @param {Object.} obj The object in which to look for val. + * @param {V} val The value for which to check. + * @return {boolean} true If the map contains the value. + * @template K,V */ -goog.events.Event.prototype.preventDefault = function() { - this.defaultPrevented = true; - this.returnValue_ = false; +goog.object.containsValue = function(obj, val) { + for (var key in obj) { + if (obj[key] == val) { + return true; + } + } + return false; }; /** - * Stops the propagation of the event. It is equivalent to - * {@code e.stopPropagation()}, but can be used as the callback argument of - * {@link goog.events.listen} without declaring another function. - * @param {!goog.events.Event} e An event. + * Searches an object for an element that satisfies the given condition and + * returns its key. + * @param {Object.} obj The object to search in. + * @param {function(this:T,V,string,Object.):boolean} f The + * function to call for every element. Takes 3 arguments (the value, + * the key and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {string|undefined} The key of an element for which the function + * returns true or undefined if no such element is found. + * @template T,K,V */ -goog.events.Event.stopPropagation = function(e) { - e.stopPropagation(); +goog.object.findKey = function(obj, f, opt_this) { + for (var key in obj) { + if (f.call(opt_this, obj[key], key, obj)) { + return key; + } + } + return undefined; }; /** - * Prevents the default action. It is equivalent to - * {@code e.preventDefault()}, but can be used as the callback argument of - * {@link goog.events.listen} without declaring another function. - * @param {!goog.events.Event} e An event. + * Searches an object for an element that satisfies the given condition and + * returns its value. + * @param {Object.} obj The object to search in. + * @param {function(this:T,V,string,Object.):boolean} f The function + * to call for every element. Takes 3 arguments (the value, the key + * and the object) and should return a boolean. + * @param {T=} opt_this An optional "this" context for the function. + * @return {V} The value of an element for which the function returns true or + * undefined if no such element is found. + * @template T,K,V */ -goog.events.Event.preventDefault = function(e) { - e.preventDefault(); +goog.object.findValue = function(obj, f, opt_this) { + var key = goog.object.findKey(obj, f, opt_this); + return key && obj[key]; }; -} -if(!lt.util.load.provided_QMARK_('goog.events.Listenable')) { -// Copyright 2012 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview An interface for a listenable JavaScript object. + * Whether the object/map/hash is empty. * - * WARNING(chrishenry): DO NOT USE! SUPPORT NOT FULLY IMPLEMENTED. + * @param {Object} obj The object to test. + * @return {boolean} true if obj is empty. */ - -goog.provide('goog.events.Listenable'); -goog.provide('goog.events.ListenableKey'); - -goog.require('goog.events.EventLike'); - +goog.object.isEmpty = function(obj) { + for (var key in obj) { + return false; + } + return true; +}; /** - * A listenable interface. Also see goog.events.EventTarget. - * @interface + * Removes all key value pairs from the object/map/hash. + * + * @param {Object} obj The object to clear. */ -goog.events.Listenable = function() {}; +goog.object.clear = function(obj) { + for (var i in obj) { + delete obj[i]; + } +}; /** - * Whether to use the new listenable interface and mechanism in - * goog.events and goog.events.EventTarget. - * - * TODO(user): Remove this once launched and stable. + * Removes a key-value pair based on the key. * - * @type {boolean} + * @param {Object} obj The object from which to remove the key. + * @param {*} key The key to remove. + * @return {boolean} Whether an element was removed. */ -goog.events.Listenable.USE_LISTENABLE_INTERFACE = false; +goog.object.remove = function(obj, key) { + var rv; + if ((rv = key in obj)) { + delete obj[key]; + } + return rv; +}; /** - * An expando property to indicate that an object implements - * goog.events.Listenable. - * - * See addImplementation/isImplementedBy. + * Adds a key-value pair to the object. Throws an exception if the key is + * already in use. Use set if you want to change an existing pair. * - * @type {string} - * @const - * @private + * @param {Object.} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} val The value to add. + * @template K,V */ -goog.events.Listenable.IMPLEMENTED_BY_PROP_ = '__closure_listenable'; +goog.object.add = function(obj, key, val) { + if (key in obj) { + throw Error('The object already contains the key "' + key + '"'); + } + goog.object.set(obj, key, val); +}; /** - * Marks a given class (constructor) as an implementation of - * Listenable, do that we can query that fact at runtime. The class - * must have already implemented the interface. - * @param {!Function} cls The class constructor. The corresponding - * class must have already implemented the interface. + * Returns the value for the given key. + * + * @param {Object.} obj The object from which to get the value. + * @param {string} key The key for which to get the value. + * @param {R=} opt_val The value to return if no item is found for the given + * key (default is undefined). + * @return {V|R|undefined} The value for the given key. + * @template K,V,R */ -goog.events.Listenable.addImplementation = function(cls) { - cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP_] = true; +goog.object.get = function(obj, key, opt_val) { + if (key in obj) { + return obj[key]; + } + return opt_val; }; /** - * @param {Object} obj The object to check. - * @return {boolean} Whether a given instance implements - * Listenable. The class/superclass of the instance must call - * addImplementation. + * Adds a key-value pair to the object/map/hash. + * + * @param {Object.} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add. + * @template K,V */ -goog.events.Listenable.isImplementedBy = function(obj) { - return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP_]); +goog.object.set = function(obj, key, value) { + obj[key] = value; }; /** - * Adds an event listener. A listener can only be added once to an - * object and if it is added again the key for the listener is - * returned. Note that if the existing listener is a one-off listener - * (registered via listenOnce), it will no longer be a one-off - * listener after a call to listen(). + * Adds a key-value pair to the object/map/hash if it doesn't exist yet. * - * @param {string} type Event type or array of event types. - * @param {!Function} listener Callback method, or an object - * with a handleEvent function. - * @param {boolean=} opt_useCapture Whether to fire in capture phase - * (defaults to false). - * @param {Object=} opt_listenerScope Object in whose scope to call the - * listener. - * @return {goog.events.ListenableKey} Unique key for the listener. + * @param {Object.} obj The object to which to add the key-value pair. + * @param {string} key The key to add. + * @param {V} value The value to add if the key wasn't present. + * @return {V} The value of the entry at the end of the function. + * @template K,V */ -goog.events.Listenable.prototype.listen; +goog.object.setIfUndefined = function(obj, key, value) { + return key in obj ? obj[key] : (obj[key] = value); +}; /** - * Adds an event listener that is removed automatically after the - * listener fired once. - * - * If an existing listener already exists, listenOnce will do - * nothing. In particular, if the listener was previously registered - * via listen(), listenOnce() will not turn the listener into a - * one-off listener. Similarly, if there is already an existing - * one-off listener, listenOnce does not modify the listeners (it is - * still a once listener). + * Does a flat clone of the object. * - * @param {string} type Event type or array of event types. - * @param {!Function} listener Callback method, or an object - * with a handleEvent function. - * @param {boolean=} opt_useCapture Whether to fire in capture phase - * (defaults to false). - * @param {Object=} opt_listenerScope Object in whose scope to call the - * listener. - * @return {goog.events.ListenableKey} Unique key for the listener. + * @param {Object.} obj Object to clone. + * @return {!Object.} Clone of the input object. + * @template K,V */ -goog.events.Listenable.prototype.listenOnce; +goog.object.clone = function(obj) { + // We cannot use the prototype trick because a lot of methods depend on where + // the actual key is set. + + var res = {}; + for (var key in obj) { + res[key] = obj[key]; + } + return res; + // We could also use goog.mixin but I wanted this to be independent from that. +}; /** - * Removes an event listener which was added with listen() or listenOnce(). + * Clones a value. The input may be an Object, Array, or basic type. Objects and + * arrays will be cloned recursively. + * + * WARNINGS: + * goog.object.unsafeClone does not detect reference loops. Objects + * that refer to themselves will cause infinite recursion. * - * Implementation needs to call goog.events.cleanUp. + * goog.object.unsafeClone is unaware of unique identifiers, and + * copies UIDs created by getUid into cloned results. * - * @param {string} type Event type or array of event types. - * @param {!Function} listener Callback method, or an object - * with a handleEvent function. TODO(user): Consider whether - * we can remove Object. - * @param {boolean=} opt_useCapture Whether to fire in capture phase - * (defaults to false). - * @param {Object=} opt_listenerScope Object in whose scope to call - * the listener. - * @return {boolean} Whether any listener was removed. + * @param {*} obj The value to clone. + * @return {*} A clone of the input value. */ -goog.events.Listenable.prototype.unlisten; +goog.object.unsafeClone = function(obj) { + var type = goog.typeOf(obj); + if (type == 'object' || type == 'array') { + if (obj.clone) { + return obj.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in obj) { + clone[key] = goog.object.unsafeClone(obj[key]); + } + return clone; + } + + return obj; +}; /** - * Removes an event listener which was added with listen() by the key - * returned by listen(). - * - * Implementation needs to call goog.events.cleanUp. + * Returns a new object in which all the keys and values are interchanged + * (keys become values and values become keys). If multiple keys map to the + * same value, the chosen transposed value is implementation-dependent. * - * @param {goog.events.ListenableKey} key The key returned by - * listen() or listenOnce(). - * @return {boolean} Whether any listener was removed. + * @param {Object} obj The object to transpose. + * @return {!Object} The transposed object. */ -goog.events.Listenable.prototype.unlistenByKey; +goog.object.transpose = function(obj) { + var transposed = {}; + for (var key in obj) { + transposed[obj[key]] = key; + } + return transposed; +}; /** - * Dispatches an event (or event like object) and calls all listeners - * listening for events of this type. The type of the event is decided by the - * type property on the event object. - * - * If any of the listeners returns false OR calls preventDefault then this - * function will return false. If one of the capture listeners calls - * stopPropagation, then the bubble listeners won't fire. - * - * @param {goog.events.EventLike} e Event object. - * @return {boolean} If anyone called preventDefault on the event object (or - * if any of the listeners returns false this will also return false. + * The names of the fields that are defined on Object.prototype. + * @type {Array.} + * @private */ -goog.events.Listenable.prototype.dispatchEvent; +goog.object.PROTOTYPE_FIELDS_ = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' +]; /** - * Removes all listeners from this listenable. If type is specified, - * it will only remove listeners of the particular type. otherwise all - * registered listeners will be removed. + * Extends an object with another object. + * This operates 'in-place'; it does not create a new Object. * - * Implementation needs to call goog.events.cleanUp for each removed - * listener. + * Example: + * var o = {}; + * goog.object.extend(o, {a: 0, b: 1}); + * o; // {a: 0, b: 1} + * goog.object.extend(o, {b: 2, c: 3}); + * o; // {a: 0, b: 2, c: 3} * - * @param {string=} opt_type Type of event to remove, default is to - * remove all types. - * @return {number} Number of listeners removed. + * @param {Object} target The object to modify. Existing properties will be + * overwritten if they are also present in one of the objects in + * {@code var_args}. + * @param {...Object} var_args The objects from which values will be copied. */ -goog.events.Listenable.prototype.removeAllListeners; +goog.object.extend = function(target, var_args) { + var key, source; + for (var i = 1; i < arguments.length; i++) { + source = arguments[i]; + for (key in source) { + target[key] = source[key]; + } + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example isPrototypeOf from + // Object.prototype) and it will also not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). -/** - * Fires all registered listeners in this listenable for the given - * type and capture mode, passing them the given eventObject. This - * does not perform actual capture/bubble. Only implementors of the - * interface should be using this. - * - * @param {string} type The type of the listeners to fire. - * @param {boolean} capture The capture mode of the listeners to fire. - * @param {goog.events.Event} eventObject The event object to fire. - * @return {boolean} Whether all listeners succeeded without - * attempting to prevent default behavior. If any listener returns - * false or called goog.events.Event#preventDefault, this returns - * false. - */ -goog.events.Listenable.prototype.fireListeners; + for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) { + key = goog.object.PROTOTYPE_FIELDS_[j]; + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } +}; /** - * Gets all listeners in this listenable for the given type and - * capture mode. - * - * @param {string} type The type of the listeners to fire. - * @param {boolean} capture The capture mode of the listeners to fire. - * @return {!Array.} An array of registered - * listeners. + * Creates a new object built from the key-value pairs provided as arguments. + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise even arguments are used as + * the property names and odd arguments are used as the property values. + * @return {!Object} The new object. + * @throws {Error} If there are uneven number of arguments or there is only one + * non array argument. */ -goog.events.Listenable.prototype.getListeners; - +goog.object.create = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.create.apply(null, arguments[0]); + } -/** - * Gets the goog.events.ListenableKey for the event or null if no such - * listener is in use. - * - * @param {string} type The name of the event without the 'on' prefix. - * @param {!Function} listener The listener function to get. - * @param {boolean=} capture Whether the listener is a capturing listener. - * @param {Object=} opt_listenerScope Object in whose scope to call the - * listener. - * @return {goog.events.ListenableKey} the found listener or null if not found. - */ -goog.events.Listenable.prototype.getListener; + if (argLength % 2) { + throw Error('Uneven number of arguments'); + } + var rv = {}; + for (var i = 0; i < argLength; i += 2) { + rv[arguments[i]] = arguments[i + 1]; + } + return rv; +}; /** - * Whether there is any active listeners matching the specified - * signature. If either the type or capture parameters are - * unspecified, the function will match on the remaining criteria. - * - * @param {string=} opt_type Event type. - * @param {boolean=} opt_capture Whether to check for capture or bubble - * listeners. - * @return {boolean} Whether there is any active listeners matching - * the requested type and/or capture phase. + * Creates a new object where the property names come from the arguments but + * the value is always set to true + * @param {...*} var_args If only one argument is provided and it is an array + * then this is used as the arguments, otherwise the arguments are used + * as the property names. + * @return {!Object} The new object. */ -goog.events.Listenable.prototype.hasListener; - - +goog.object.createSet = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.createSet.apply(null, arguments[0]); + } -/** - * An interface that describes a single registered listener. - * @interface - */ -goog.events.ListenableKey = function() {}; + var rv = {}; + for (var i = 0; i < argLength; i++) { + rv[arguments[i]] = true; + } + return rv; +}; /** - * Counter used to create a unique key - * @type {number} - * @private - */ -goog.events.ListenableKey.counter_ = 0; - - -/** - * Reserves a key to be used for ListenableKey#key field. - * @return {number} A number to be used to fill ListenableKey#key - * field. + * Creates an immutable view of the underlying object, if the browser + * supports immutable objects. + * + * In default mode, writes to this view will fail silently. In strict mode, + * they will throw an error. + * + * @param {!Object.} obj An object. + * @return {!Object.} An immutable view of that object, or the + * original object if this browser does not support immutables. + * @template K,V */ -goog.events.ListenableKey.reserveKey = function() { - return ++goog.events.ListenableKey.counter_; +goog.object.createImmutableView = function(obj) { + var result = obj; + if (Object.isFrozen && !Object.isFrozen(obj)) { + result = Object.create(obj); + Object.freeze(result); + } + return result; }; /** - * The source event target. - * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)} - */ -goog.events.ListenableKey.prototype.src; - - -/** - * The event type the listener is listening to. - * @type {string} - */ -goog.events.ListenableKey.prototype.type; - - -/** - * The listener function. - * TODO(user): Narrow the type if possible. - * @type {Function|Object} - */ -goog.events.ListenableKey.prototype.listener; - - -/** - * Whether the listener works on capture phase. - * @type {boolean} - */ -goog.events.ListenableKey.prototype.capture; - - -/** - * The 'this' object for the listener function's scope. - * @type {Object} - */ -goog.events.ListenableKey.prototype.handler; - - -/** - * A globally unique number to identify the key. - * @type {number} + * @param {!Object} obj An object. + * @return {boolean} Whether this is an immutable view of the object. */ -goog.events.ListenableKey.prototype.key; +goog.object.isImmutableView = function(obj) { + return !!Object.isFrozen && Object.isFrozen(obj); +}; } -if(!lt.util.load.provided_QMARK_('goog.userAgent')) { -// Copyright 2006 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.events.ListenerMap')) { +// Copyright 2013 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -1567,569 +1925,722 @@ if(!lt.util.load.provided_QMARK_('goog.userAgent')) { // limitations under the License. /** - * @fileoverview Rendering engine detection. - * @see User agent strings - * For information on the browser brand (such as Safari versus Chrome), see - * goog.userAgent.product. - * @see ../demos/useragent.html + * @fileoverview A map of listeners that provides utility functions to + * deal with listeners on an event target. Used by + * {@code goog.events.EventTarget}. + * + * WARNING: Do not use this class from outside goog.events package. + * + * @visibility {//closure/goog/bin/sizetests:__pkg__} + * @visibility {//closure/goog/events:__pkg__} + * @visibility {//closure/goog/labs/events:__pkg__} */ -goog.provide('goog.userAgent'); +goog.provide('goog.events.ListenerMap'); + +goog.require('goog.array'); +goog.require('goog.events.Listener'); +goog.require('goog.object'); -goog.require('goog.string'); /** - * @define {boolean} Whether we know at compile-time that the browser is IE. + * Creates a new listener map. + * @param {EventTarget|goog.events.Listenable} src The src object. + * @constructor + * @final */ -goog.userAgent.ASSUME_IE = false; +goog.events.ListenerMap = function(src) { + /** @type {EventTarget|goog.events.Listenable} */ + this.src = src; + /** + * Maps of event type to an array of listeners. + * @type {Object.>} + */ + this.listeners = {}; -/** - * @define {boolean} Whether we know at compile-time that the browser is GECKO. - */ -goog.userAgent.ASSUME_GECKO = false; + /** + * The count of types in this map that have registered listeners. + * @private {number} + */ + this.typeCount_ = 0; +}; /** - * @define {boolean} Whether we know at compile-time that the browser is WEBKIT. + * @return {number} The count of event types in this map that actually + * have registered listeners. */ -goog.userAgent.ASSUME_WEBKIT = false; +goog.events.ListenerMap.prototype.getTypeCount = function() { + return this.typeCount_; +}; /** - * @define {boolean} Whether we know at compile-time that the browser is a - * mobile device running WebKit e.g. iPhone or Android. + * @return {number} Total number of registered listeners. */ -goog.userAgent.ASSUME_MOBILE_WEBKIT = false; +goog.events.ListenerMap.prototype.getListenerCount = function() { + var count = 0; + for (var type in this.listeners) { + count += this.listeners[type].length; + } + return count; +}; /** - * @define {boolean} Whether we know at compile-time that the browser is OPERA. + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. + * + * Note that a one-off listener will not change an existing listener, + * if any. On the other hand a normal listener will change existing + * one-off listener to become a normal listener. + * + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean} callOnce Whether the listener is a one-off + * listener. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. */ -goog.userAgent.ASSUME_OPERA = false; +goog.events.ListenerMap.prototype.add = function( + type, listener, callOnce, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + var listenerArray = this.listeners[typeStr]; + if (!listenerArray) { + listenerArray = this.listeners[typeStr] = []; + this.typeCount_++; + } + + var listenerObj; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + listenerObj = listenerArray[index]; + if (!callOnce) { + // Ensure that, if there is an existing callOnce listener, it is no + // longer a callOnce listener. + listenerObj.callOnce = false; + } + } else { + listenerObj = new goog.events.Listener( + listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope); + listenerObj.callOnce = callOnce; + listenerArray.push(listenerObj); + } + return listenerObj; +}; /** - * @define {boolean} Whether the {@code goog.userAgent.isVersion} function will - * return true for any version. + * Removes a matching listener. + * @param {string|!goog.events.EventId} type The listener event type. + * @param {!Function} listener This listener callback method. + * @param {boolean=} opt_useCapture The capture mode of the listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {boolean} Whether any listener was removed. */ -goog.userAgent.ASSUME_ANY_VERSION = false; +goog.events.ListenerMap.prototype.remove = function( + type, listener, opt_useCapture, opt_listenerScope) { + var typeStr = type.toString(); + if (!(typeStr in this.listeners)) { + return false; + } + + var listenerArray = this.listeners[typeStr]; + var index = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, opt_useCapture, opt_listenerScope); + if (index > -1) { + var listenerObj = listenerArray[index]; + listenerObj.markAsRemoved(); + goog.array.removeAt(listenerArray, index); + if (listenerArray.length == 0) { + delete this.listeners[typeStr]; + this.typeCount_--; + } + return true; + } + return false; +}; /** - * Whether we know the browser engine at compile-time. - * @type {boolean} - * @private + * Removes the given listener object. + * @param {goog.events.ListenableKey} listener The listener to remove. + * @return {boolean} Whether the listener is removed. */ -goog.userAgent.BROWSER_KNOWN_ = - goog.userAgent.ASSUME_IE || - goog.userAgent.ASSUME_GECKO || - goog.userAgent.ASSUME_MOBILE_WEBKIT || - goog.userAgent.ASSUME_WEBKIT || - goog.userAgent.ASSUME_OPERA; +goog.events.ListenerMap.prototype.removeByKey = function(listener) { + var type = listener.type; + if (!(type in this.listeners)) { + return false; + } + + var removed = goog.array.remove(this.listeners[type], listener); + if (removed) { + listener.markAsRemoved(); + if (this.listeners[type].length == 0) { + delete this.listeners[type]; + this.typeCount_--; + } + } + return removed; +}; /** - * Returns the userAgent string for the current browser. - * Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a - * navigator object off the global scope. In that case we return null. - * - * @return {?string} The userAgent string or null if there is none. + * Removes all listeners from this map. If opt_type is provided, only + * listeners that match the given type are removed. + * @param {string|!goog.events.EventId=} opt_type Type of event to remove. + * @return {number} Number of listeners removed. */ -goog.userAgent.getUserAgentString = function() { - return goog.global['navigator'] ? goog.global['navigator'].userAgent : null; +goog.events.ListenerMap.prototype.removeAll = function(opt_type) { + var typeStr = opt_type && opt_type.toString(); + var count = 0; + for (var type in this.listeners) { + if (!typeStr || type == typeStr) { + var listenerArray = this.listeners[type]; + for (var i = 0; i < listenerArray.length; i++) { + ++count; + listenerArray[i].markAsRemoved(); + } + delete this.listeners[type]; + this.typeCount_--; + } + } + return count; }; /** - * @return {Object} The native navigator object. + * Gets all listeners that match the given type and capture mode. The + * returned array is a copy (but the listener objects are not). + * @param {string|!goog.events.EventId} type The type of the listeners + * to retrieve. + * @param {boolean} capture The capture mode of the listeners to retrieve. + * @return {!Array.} An array of matching + * listeners. */ -goog.userAgent.getNavigator = function() { - // Need a local navigator reference instead of using the global one, - // to avoid the rare case where they reference different objects. - // (in a WorkerPool, for example). - return goog.global['navigator']; +goog.events.ListenerMap.prototype.getListeners = function(type, capture) { + var listenerArray = this.listeners[type.toString()]; + var rv = []; + if (listenerArray) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (listenerObj.capture == capture) { + rv.push(listenerObj); + } + } + } + return rv; }; /** - * Initializer for goog.userAgent. + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. * - * This is a named function so that it can be stripped via the jscompiler - * option for stripping types. - * @private + * @param {string|!goog.events.EventId} type The type of the listener + * to retrieve. + * @param {!Function} listener The listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {Object=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. */ -goog.userAgent.init_ = function() { - /** - * Whether the user agent string denotes Opera. - * @type {boolean} - * @private - */ - goog.userAgent.detectedOpera_ = false; - - /** - * Whether the user agent string denotes Internet Explorer. This includes - * other browsers using Trident as its rendering engine. For example AOL - * and Netscape 8 - * @type {boolean} - * @private - */ - goog.userAgent.detectedIe_ = false; - - /** - * Whether the user agent string denotes WebKit. WebKit is the rendering - * engine that Safari, Android and others use. - * @type {boolean} - * @private - */ - goog.userAgent.detectedWebkit_ = false; - - /** - * Whether the user agent string denotes a mobile device. - * @type {boolean} - * @private - */ - goog.userAgent.detectedMobile_ = false; - - /** - * Whether the user agent string denotes Gecko. Gecko is the rendering - * engine used by Mozilla, Mozilla Firefox, Camino and many more. - * @type {boolean} - * @private - */ - goog.userAgent.detectedGecko_ = false; - - var ua; - if (!goog.userAgent.BROWSER_KNOWN_ && - (ua = goog.userAgent.getUserAgentString())) { - var navigator = goog.userAgent.getNavigator(); - goog.userAgent.detectedOpera_ = ua.indexOf('Opera') == 0; - goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ && - ua.indexOf('MSIE') != -1; - goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ && - ua.indexOf('WebKit') != -1; - // WebKit also gives navigator.product string equal to 'Gecko'. - goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ && - ua.indexOf('Mobile') != -1; - goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ && - !goog.userAgent.detectedWebkit_ && navigator.product == 'Gecko'; +goog.events.ListenerMap.prototype.getListener = function( + type, listener, capture, opt_listenerScope) { + var listenerArray = this.listeners[type.toString()]; + var i = -1; + if (listenerArray) { + i = goog.events.ListenerMap.findListenerIndex_( + listenerArray, listener, capture, opt_listenerScope); } + return i > -1 ? listenerArray[i] : null; }; -if (!goog.userAgent.BROWSER_KNOWN_) { - goog.userAgent.init_(); -} - - /** - * Whether the user agent is Opera. - * @type {boolean} + * Whether there is a matching listener. If either the type or capture + * parameters are unspecified, the function will match on the + * remaining criteria. + * + * @param {string|!goog.events.EventId=} opt_type The type of the listener. + * @param {boolean=} opt_capture The capture mode of the listener. + * @return {boolean} Whether there is an active listener matching + * the requested type and/or capture phase. */ -goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? - goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_; +goog.events.ListenerMap.prototype.hasListener = function( + opt_type, opt_capture) { + var hasType = goog.isDef(opt_type); + var typeStr = hasType ? opt_type.toString() : ''; + var hasCapture = goog.isDef(opt_capture); + + return goog.object.some( + this.listeners, function(listenerArray, type) { + for (var i = 0; i < listenerArray.length; ++i) { + if ((!hasType || listenerArray[i].type == typeStr) && + (!hasCapture || listenerArray[i].capture == opt_capture)) { + return true; + } + } + + return false; + }); +}; /** - * Whether the user agent is Internet Explorer. This includes other browsers - * using Trident as its rendering engine. For example AOL and Netscape 8 - * @type {boolean} + * Finds the index of a matching goog.events.Listener in the given + * listenerArray. + * @param {!Array.} listenerArray Array of listener. + * @param {!Function} listener The listener function. + * @param {boolean=} opt_useCapture The capture flag for the listener. + * @param {Object=} opt_listenerScope The listener scope. + * @return {number} The index of the matching listener within the + * listenerArray. + * @private */ -goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? - goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_; - +goog.events.ListenerMap.findListenerIndex_ = function( + listenerArray, listener, opt_useCapture, opt_listenerScope) { + for (var i = 0; i < listenerArray.length; ++i) { + var listenerObj = listenerArray[i]; + if (!listenerObj.removed && + listenerObj.listener == listener && + listenerObj.capture == !!opt_useCapture && + listenerObj.handler == opt_listenerScope) { + return i; + } + } + return -1; +}; +} +if(!lt.util.load.provided_QMARK_('goog.reflect')) { +// 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. /** - * Whether the user agent is Gecko. Gecko is the rendering engine used by - * Mozilla, Mozilla Firefox, Camino and many more. - * @type {boolean} + * @fileoverview Useful compiler idioms. + * */ -goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? - goog.userAgent.ASSUME_GECKO : - goog.userAgent.detectedGecko_; + +goog.provide('goog.reflect'); /** - * Whether the user agent is WebKit. WebKit is the rendering engine that - * Safari, Android and others use. - * @type {boolean} + * Syntax for object literal casts. + * @see http://go/jscompiler-renaming + * @see http://code.google.com/p/closure-compiler/wiki/ + * ExperimentalTypeBasedPropertyRenaming + * + * Use this if you have an object literal whose keys need to have the same names + * as the properties of some class even after they are renamed by the compiler. + * + * @param {!Function} type Type to cast to. + * @param {Object} object Object literal to cast. + * @return {Object} The object literal. */ -goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? - goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : - goog.userAgent.detectedWebkit_; +goog.reflect.object = function(type, object) { + return object; +}; /** - * Whether the user agent is running on a mobile device. - * @type {boolean} + * To assert to the compiler that an operation is needed when it would + * otherwise be stripped. For example: + * + * // Force a layout + * goog.reflect.sinkValue(dialog.offsetHeight); + * + * @type {!Function} */ -goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT || - goog.userAgent.detectedMobile_; +goog.reflect.sinkValue = function(x) { + goog.reflect.sinkValue[' '](x); + return x; +}; /** - * Used while transitioning code to use WEBKIT instead. - * @type {boolean} - * @deprecated Use {@link goog.userAgent.product.SAFARI} instead. - * TODO(nicksantos): Delete this from goog.userAgent. + * The compiler should optimize this function away iff no one ever uses + * goog.reflect.sinkValue. */ -goog.userAgent.SAFARI = goog.userAgent.WEBKIT; +goog.reflect.sinkValue[' '] = goog.nullFunction; /** - * @return {string} the platform (operating system) the user agent is running - * on. Default to empty string because navigator.platform may not be defined - * (on Rhino, for example). - * @private + * Check if a property can be accessed without throwing an exception. + * @param {Object} obj The owner of the property. + * @param {string} prop The property name. + * @return {boolean} Whether the property is accessible. Will also return true + * if obj is null. */ -goog.userAgent.determinePlatform_ = function() { - var navigator = goog.userAgent.getNavigator(); - return navigator && navigator.platform || ''; +goog.reflect.canAccessProperty = function(obj, prop) { + /** @preserveTry */ + try { + goog.reflect.sinkValue(obj[prop]); + return true; + } catch (e) {} + return false; }; +} +if(!lt.util.load.provided_QMARK_('goog.events.Listenable')) { +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An interface for a listenable JavaScript object. + */ + +goog.provide('goog.events.Listenable'); +goog.provide('goog.events.ListenableKey'); + +/** @suppress {extraRequire} */ +goog.require('goog.events.EventId'); + /** - * The platform (operating system) the user agent is running on. Default to - * empty string because navigator.platform may not be defined (on Rhino, for - * example). + * A listenable interface. A listenable is an object with the ability + * to dispatch/broadcast events to "event listeners" registered via + * listen/listenOnce. + * + * The interface allows for an event propagation mechanism similar + * to one offered by native browser event targets, such as + * capture/bubble mechanism, stopping propagation, and preventing + * default actions. Capture/bubble mechanism depends on the ancestor + * tree constructed via {@code #getParentEventTarget}; this tree + * must be directed acyclic graph. The meaning of default action(s) + * in preventDefault is specific to a particular use case. + * + * Implementations that do not support capture/bubble or can not have + * a parent listenable can simply not implement any ability to set the + * parent listenable (and have {@code #getParentEventTarget} return + * null). + * + * Implementation of this class can be used with or independently from + * goog.events. + * + * Implementation must call {@code #addImplementation(implClass)}. + * + * @interface + * @see goog.events + * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html + */ +goog.events.Listenable = function() {}; + + +/** + * An expando property to indicate that an object implements + * goog.events.Listenable. + * + * See addImplementation/isImplementedBy. + * * @type {string} + * @const */ -goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); +goog.events.Listenable.IMPLEMENTED_BY_PROP = + 'closure_listenable_' + ((Math.random() * 1e6) | 0); /** - * @define {boolean} Whether the user agent is running on a Macintosh operating - * system. + * Marks a given class (constructor) as an implementation of + * Listenable, do that we can query that fact at runtime. The class + * must have already implemented the interface. + * @param {!Function} cls The class constructor. The corresponding + * class must have already implemented the interface. */ -goog.userAgent.ASSUME_MAC = false; +goog.events.Listenable.addImplementation = function(cls) { + cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true; +}; /** - * @define {boolean} Whether the user agent is running on a Windows operating - * system. + * @param {Object} obj The object to check. + * @return {boolean} Whether a given instance implements + * Listenable. The class/superclass of the instance must call + * addImplementation. */ -goog.userAgent.ASSUME_WINDOWS = false; +goog.events.Listenable.isImplementedBy = function(obj) { + try { + return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]); + } catch (e) { + return false; + } +}; /** - * @define {boolean} Whether the user agent is running on a Linux operating - * system. + * Adds an event listener. A listener can only be added once to an + * object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). + * + * @param {string|!goog.events.EventId.} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ */ -goog.userAgent.ASSUME_LINUX = false; +goog.events.Listenable.prototype.listen; /** - * @define {boolean} Whether the user agent is running on a X11 windowing - * system. + * Adds an event listener that is removed automatically after the + * listener fired once. + * + * If an existing listener already exists, listenOnce will do + * nothing. In particular, if the listener was previously registered + * via listen(), listenOnce() will not turn the listener into a + * one-off listener. Similarly, if there is already an existing + * one-off listener, listenOnce does not modify the listeners (it is + * still a once listener). + * + * @param {string|!goog.events.EventId.} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} Unique key for the listener. + * @template SCOPE,EVENTOBJ */ -goog.userAgent.ASSUME_X11 = false; +goog.events.Listenable.prototype.listenOnce; /** - * @define {boolean} Whether the user agent is running on Android. + * Removes an event listener which was added with listen() or listenOnce(). + * + * @param {string|!goog.events.EventId.} type The event type id. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback + * method. + * @param {boolean=} opt_useCapture Whether to fire in capture phase + * (defaults to false). + * @param {SCOPE=} opt_listenerScope Object in whose scope to call + * the listener. + * @return {boolean} Whether any listener was removed. + * @template SCOPE,EVENTOBJ */ -goog.userAgent.ASSUME_ANDROID = false; +goog.events.Listenable.prototype.unlisten; /** - * @define {boolean} Whether the user agent is running on an iPhone. + * Removes an event listener which was added with listen() by the key + * returned by listen(). + * + * @param {goog.events.ListenableKey} key The key returned by + * listen() or listenOnce(). + * @return {boolean} Whether any listener was removed. */ -goog.userAgent.ASSUME_IPHONE = false; +goog.events.Listenable.prototype.unlistenByKey; /** - * @define {boolean} Whether the user agent is running on an iPad. + * Dispatches an event (or event like object) and calls all listeners + * listening for events of this type. The type of the event is decided by the + * type property on the event object. + * + * If any of the listeners returns false OR calls preventDefault then this + * function will return false. If one of the capture listeners calls + * stopPropagation, then the bubble listeners won't fire. + * + * @param {goog.events.EventLike} e Event object. + * @return {boolean} If anyone called preventDefault on the event object (or + * if any of the listeners returns false) this will also return false. */ -goog.userAgent.ASSUME_IPAD = false; +goog.events.Listenable.prototype.dispatchEvent; /** - * @type {boolean} - * @private + * Removes all listeners from this listenable. If type is specified, + * it will only remove listeners of the particular type. otherwise all + * registered listeners will be removed. + * + * @param {string=} opt_type Type of event to remove, default is to + * remove all types. + * @return {number} Number of listeners removed. */ -goog.userAgent.PLATFORM_KNOWN_ = - goog.userAgent.ASSUME_MAC || - goog.userAgent.ASSUME_WINDOWS || - goog.userAgent.ASSUME_LINUX || - goog.userAgent.ASSUME_X11 || - goog.userAgent.ASSUME_ANDROID || - goog.userAgent.ASSUME_IPHONE || - goog.userAgent.ASSUME_IPAD; +goog.events.Listenable.prototype.removeAllListeners; /** - * Initialize the goog.userAgent constants that define which platform the user - * agent is running on. - * @private + * Returns the parent of this event target to use for capture/bubble + * mechanism. + * + * NOTE(user): The name reflects the original implementation of + * custom event target ({@code goog.events.EventTarget}). We decided + * that changing the name is not worth it. + * + * @return {goog.events.Listenable} The parent EventTarget or null if + * there is no parent. */ -goog.userAgent.initPlatform_ = function() { - /** - * Whether the user agent is running on a Macintosh operating system. - * @type {boolean} - * @private - */ - goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM, - 'Mac'); - - /** - * Whether the user agent is running on a Windows operating system. - * @type {boolean} - * @private - */ - goog.userAgent.detectedWindows_ = goog.string.contains( - goog.userAgent.PLATFORM, 'Win'); - - /** - * Whether the user agent is running on a Linux operating system. - * @type {boolean} - * @private - */ - goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM, - 'Linux'); - - /** - * Whether the user agent is running on a X11 windowing system. - * @type {boolean} - * @private - */ - goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() && - goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '', - 'X11'); - - // Need user agent string for Android/IOS detection - var ua = goog.userAgent.getUserAgentString(); - - /** - * Whether the user agent is running on Android. - * @type {boolean} - * @private - */ - goog.userAgent.detectedAndroid_ = !!ua && ua.indexOf('Android') >= 0; - - /** - * Whether the user agent is running on an iPhone. - * @type {boolean} - * @private - */ - goog.userAgent.detectedIPhone_ = !!ua && ua.indexOf('iPhone') >= 0; - - /** - * Whether the user agent is running on an iPad. - * @type {boolean} - * @private - */ - goog.userAgent.detectedIPad_ = !!ua && ua.indexOf('iPad') >= 0; -}; - - -if (!goog.userAgent.PLATFORM_KNOWN_) { - goog.userAgent.initPlatform_(); -} - - -/** - * Whether the user agent is running on a Macintosh operating system. - * @type {boolean} - */ -goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_; +goog.events.Listenable.prototype.getParentEventTarget; /** - * Whether the user agent is running on a Windows operating system. - * @type {boolean} + * Fires all registered listeners in this listenable for the given + * type and capture mode, passing them the given eventObject. This + * does not perform actual capture/bubble. Only implementors of the + * interface should be using this. + * + * @param {string|!goog.events.EventId.} type The type of the + * listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @param {EVENTOBJ} eventObject The event object to fire. + * @return {boolean} Whether all listeners succeeded without + * attempting to prevent default behavior. If any listener returns + * false or called goog.events.Event#preventDefault, this returns + * false. + * @template EVENTOBJ */ -goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_; +goog.events.Listenable.prototype.fireListeners; /** - * Whether the user agent is running on a Linux operating system. - * @type {boolean} + * Gets all listeners in this listenable for the given type and + * capture mode. + * + * @param {string|!goog.events.EventId} type The type of the listeners to fire. + * @param {boolean} capture The capture mode of the listeners to fire. + * @return {!Array.} An array of registered + * listeners. + * @template EVENTOBJ */ -goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_; +goog.events.Listenable.prototype.getListeners; /** - * Whether the user agent is running on a X11 windowing system. - * @type {boolean} + * Gets the goog.events.ListenableKey for the event or null if no such + * listener is in use. + * + * @param {string|!goog.events.EventId.} type The name of the event + * without the 'on' prefix. + * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The + * listener function to get. + * @param {boolean} capture Whether the listener is a capturing listener. + * @param {SCOPE=} opt_listenerScope Object in whose scope to call the + * listener. + * @return {goog.events.ListenableKey} the found listener or null if not found. + * @template SCOPE,EVENTOBJ */ -goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_; +goog.events.Listenable.prototype.getListener; /** - * Whether the user agent is running on Android. - * @type {boolean} + * Whether there is any active listeners matching the specified + * signature. If either the type or capture parameters are + * unspecified, the function will match on the remaining criteria. + * + * @param {string|!goog.events.EventId.=} opt_type Event type. + * @param {boolean=} opt_capture Whether to check for capture or bubble + * listeners. + * @return {boolean} Whether there is any active listeners matching + * the requested type and/or capture phase. + * @template EVENTOBJ */ -goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_; - +goog.events.Listenable.prototype.hasListener; -/** - * Whether the user agent is running on an iPhone. - * @type {boolean} - */ -goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_; /** - * Whether the user agent is running on an iPad. - * @type {boolean} + * An interface that describes a single registered listener. + * @interface */ -goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ? - goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_; +goog.events.ListenableKey = function() {}; /** - * @return {string} The string that describes the version number of the user - * agent. + * Counter used to create a unique key + * @type {number} * @private */ -goog.userAgent.determineVersion_ = function() { - // All browsers have different ways to detect the version and they all have - // different naming schemes. - - // version is a string rather than a number because it may contain 'b', 'a', - // and so on. - var version = '', re; - - if (goog.userAgent.OPERA && goog.global['opera']) { - var operaVersion = goog.global['opera'].version; - version = typeof operaVersion == 'function' ? operaVersion() : operaVersion; - } else { - if (goog.userAgent.GECKO) { - re = /rv\:([^\);]+)(\)|;)/; - } else if (goog.userAgent.IE) { - re = /MSIE\s+([^\);]+)(\)|;)/; - } else if (goog.userAgent.WEBKIT) { - // WebKit/125.4 - re = /WebKit\/(\S+)/; - } - if (re) { - var arr = re.exec(goog.userAgent.getUserAgentString()); - version = arr ? arr[1] : ''; - } - } - if (goog.userAgent.IE) { - // IE9 can be in document mode 9 but be reporting an inconsistent user agent - // version. If it is identifying as a version lower than 9 we take the - // documentMode as the version instead. IE8 has similar behavior. - // It is recommended to set the X-UA-Compatible header to ensure that IE9 - // uses documentMode 9. - var docMode = goog.userAgent.getDocumentMode_(); - if (docMode > parseFloat(version)) { - return String(docMode); - } - } - return version; -}; +goog.events.ListenableKey.counter_ = 0; /** - * @return {number|undefined} Returns the document mode (for testing). - * @private + * Reserves a key to be used for ListenableKey#key field. + * @return {number} A number to be used to fill ListenableKey#key + * field. */ -goog.userAgent.getDocumentMode_ = function() { - // NOTE(user): goog.userAgent may be used in context where there is no DOM. - var doc = goog.global['document']; - return doc ? doc['documentMode'] : undefined; +goog.events.ListenableKey.reserveKey = function() { + return ++goog.events.ListenableKey.counter_; }; /** - * The version of the user agent. This is a string because it might contain - * 'b' (as in beta) as well as multiple dots. - * @type {string} + * The source event target. + * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)} */ -goog.userAgent.VERSION = goog.userAgent.determineVersion_(); +goog.events.ListenableKey.prototype.src; /** - * Compares two version numbers. - * - * @param {string} v1 Version of first item. - * @param {string} v2 Version of second item. - * - * @return {number} 1 if first argument is higher - * 0 if arguments are equal - * -1 if second argument is higher. - * @deprecated Use goog.string.compareVersions. + * The event type the listener is listening to. + * @type {string} */ -goog.userAgent.compare = function(v1, v2) { - return goog.string.compareVersions(v1, v2); -}; +goog.events.ListenableKey.prototype.type; /** - * Cache for {@link goog.userAgent.isVersion}. Calls to compareVersions are - * surprisingly expensive and as a browsers version number is unlikely to change - * during a session we cache the results. - * @type {Object} - * @private + * The listener function. + * @type {function(?):?|{handleEvent:function(?):?}|null} */ -goog.userAgent.isVersionCache_ = {}; +goog.events.ListenableKey.prototype.listener; /** - * Whether the user agent version is higher or the same as the given version. - * NOTE: When checking the version numbers for Firefox and Safari, be sure to - * use the engine's version, not the browser's version number. For example, - * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11. - * Opera and Internet Explorer versions match the product release number.
- * @see - * Webkit - * @see Gecko - * - * @param {string|number} version The version to check. - * @return {boolean} Whether the user agent version is higher or the same as - * the given version. + * Whether the listener works on capture phase. + * @type {boolean} */ -goog.userAgent.isVersion = function(version) { - return goog.userAgent.ASSUME_ANY_VERSION || - goog.userAgent.isVersionCache_[version] || - (goog.userAgent.isVersionCache_[version] = - goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0); -}; +goog.events.ListenableKey.prototype.capture; /** - * Whether the IE effective document mode is higher or the same as the given - * document mode version. - * NOTE: Only for IE, return false for another browser. - * - * @param {number} documentMode The document mode version to check. - * @return {boolean} Whether the IE effective document mode is higher or the - * same as the given version. + * The 'this' object for the listener function's scope. + * @type {Object} */ -goog.userAgent.isDocumentMode = function(documentMode) { - return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode; -}; +goog.events.ListenableKey.prototype.handler; /** - * For IE version < 7, documentMode is undefined, so attempt to use the - * CSS1Compat property to see if we are in standards mode. If we are in - * standards mode, treat the browser version as the document mode. Otherwise, - * IE is emulating version 5. - * @type {number|undefined} - * @const + * A globally unique number to identify the key. + * @type {number} */ -goog.userAgent.DOCUMENT_MODE = (function() { - var doc = goog.global['document']; - if (!doc || !goog.userAgent.IE) { - return undefined; - } - var mode = goog.userAgent.getDocumentMode_(); - return mode || (doc['compatMode'] == 'CSS1Compat' ? - parseInt(goog.userAgent.VERSION, 10) : 5); -})(); +goog.events.ListenableKey.prototype.key; } -if(!lt.util.load.provided_QMARK_('goog.events.BrowserFeature')) { -// Copyright 2010 The Closure Library Authors. All Rights Reserved. +if(!lt.util.load.provided_QMARK_('goog.userAgent')) { +// 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. @@ -2144,1386 +2655,1287 @@ if(!lt.util.load.provided_QMARK_('goog.events.BrowserFeature')) { // limitations under the License. /** - * @fileoverview Browser capability checks for the events package. - * + * @fileoverview Rendering engine detection. + * @see User agent strings + * For information on the browser brand (such as Safari versus Chrome), see + * goog.userAgent.product. + * @see ../demos/useragent.html */ +goog.provide('goog.userAgent'); -goog.provide('goog.events.BrowserFeature'); +goog.require('goog.string'); -goog.require('goog.userAgent'); + +/** + * @define {boolean} Whether we know at compile-time that the browser is IE. + */ +goog.define('goog.userAgent.ASSUME_IE', false); /** - * Enum of browser capabilities. - * @enum {boolean} + * @define {boolean} Whether we know at compile-time that the browser is GECKO. */ -goog.events.BrowserFeature = { - /** - * Whether the button attribute of the event is W3C compliant. False in - * Internet Explorer prior to version 9; document-version dependent. - */ - HAS_W3C_BUTTON: !goog.userAgent.IE || goog.userAgent.isDocumentMode(9), +goog.define('goog.userAgent.ASSUME_GECKO', false); - /** - * Whether the browser supports full W3C event model. - */ - HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE || goog.userAgent.isDocumentMode(9), +/** + * @define {boolean} Whether we know at compile-time that the browser is WEBKIT. + */ +goog.define('goog.userAgent.ASSUME_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is a + * mobile device running WebKit e.g. iPhone or Android. + */ +goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false); + + +/** + * @define {boolean} Whether we know at compile-time that the browser is OPERA. + */ +goog.define('goog.userAgent.ASSUME_OPERA', false); + + +/** + * @define {boolean} Whether the + * {@code goog.userAgent.isVersionOrHigher} + * function will return true for any version. + */ +goog.define('goog.userAgent.ASSUME_ANY_VERSION', false); + + +/** + * Whether we know the browser engine at compile-time. + * @type {boolean} + * @private + */ +goog.userAgent.BROWSER_KNOWN_ = + goog.userAgent.ASSUME_IE || + goog.userAgent.ASSUME_GECKO || + goog.userAgent.ASSUME_MOBILE_WEBKIT || + goog.userAgent.ASSUME_WEBKIT || + goog.userAgent.ASSUME_OPERA; + + +/** + * Returns the userAgent string for the current browser. + * Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a + * navigator object off the global scope. In that case we return null. + * + * @return {?string} The userAgent string or null if there is none. + */ +goog.userAgent.getUserAgentString = function() { + return goog.global['navigator'] ? goog.global['navigator'].userAgent : null; +}; + + +/** + * @return {Object} The native navigator object. + */ +goog.userAgent.getNavigator = function() { + // Need a local navigator reference instead of using the global one, + // to avoid the rare case where they reference different objects. + // (in a WorkerPool, for example). + return goog.global['navigator']; +}; + + +/** + * Initializer for goog.userAgent. + * + * This is a named function so that it can be stripped via the jscompiler + * option for stripping types. + * @private + */ +goog.userAgent.init_ = function() { /** - * To prevent default in IE7-8 for certain keydown events we need set the - * keyCode to -1. + * Whether the user agent string denotes Opera. + * @type {boolean} + * @private */ - SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE && - !goog.userAgent.isVersion('9'), + goog.userAgent.detectedOpera_ = false; /** - * Whether the {@code navigator.onLine} property is supported. + * Whether the user agent string denotes Internet Explorer. This includes + * other browsers using Trident as its rendering engine. For example AOL + * and Netscape 8 + * @type {boolean} + * @private */ - HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT || - goog.userAgent.isVersion('528'), + goog.userAgent.detectedIe_ = false; /** - * Whether HTML5 network online/offline events are supported. + * Whether the user agent string denotes WebKit. WebKit is the rendering + * engine that Safari, Android and others use. + * @type {boolean} + * @private */ - HAS_HTML5_NETWORK_EVENT_SUPPORT: - goog.userAgent.GECKO && goog.userAgent.isVersion('1.9b') || - goog.userAgent.IE && goog.userAgent.isVersion('8') || - goog.userAgent.OPERA && goog.userAgent.isVersion('9.5') || - goog.userAgent.WEBKIT && goog.userAgent.isVersion('528'), + goog.userAgent.detectedWebkit_ = false; /** - * Whether HTML5 network events fire on document.body, or otherwise the - * window. + * Whether the user agent string denotes a mobile device. + * @type {boolean} + * @private */ - HTML5_NETWORK_EVENTS_FIRE_ON_BODY: - goog.userAgent.GECKO && !goog.userAgent.isVersion('8') || - goog.userAgent.IE && !goog.userAgent.isVersion('9'), + goog.userAgent.detectedMobile_ = false; /** - * Whether touch is enabled in the browser. + * Whether the user agent string denotes Gecko. Gecko is the rendering + * engine used by Mozilla, Mozilla Firefox, Camino and many more. + * @type {boolean} + * @private */ - TOUCH_ENABLED: - ('ontouchstart' in goog.global || - !!(goog.global['document'] && - document.documentElement && - 'ontouchstart' in document.documentElement) || - // IE10 uses non-standard touch events, so it has a different check. - !!(goog.global['navigator'] && - goog.global['navigator']['msMaxTouchPoints'])) + goog.userAgent.detectedGecko_ = false; + + var ua; + if (!goog.userAgent.BROWSER_KNOWN_ && + (ua = goog.userAgent.getUserAgentString())) { + var navigator = goog.userAgent.getNavigator(); + goog.userAgent.detectedOpera_ = goog.string.startsWith(ua, 'Opera'); + goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ && + (goog.string.contains(ua, 'MSIE') || + goog.string.contains(ua, 'Trident')); + goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ && + goog.string.contains(ua, 'WebKit'); + // WebKit also gives navigator.product string equal to 'Gecko'. + goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ && + goog.string.contains(ua, 'Mobile'); + goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ && + !goog.userAgent.detectedWebkit_ && !goog.userAgent.detectedIe_ && + navigator.product == 'Gecko'; + } }; + + +if (!goog.userAgent.BROWSER_KNOWN_) { + goog.userAgent.init_(); } -if(!lt.util.load.provided_QMARK_('goog.events.EventType')) { -// Copyright 2010 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview Event Types. - * - * @author arv@google.com (Erik Arvidsson) - * @author mirkov@google.com (Mirko Visontai) + * Whether the user agent is Opera. + * @type {boolean} */ +goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_; -goog.provide('goog.events.EventType'); +/** + * Whether the user agent is Internet Explorer. This includes other browsers + * using Trident as its rendering engine. For example AOL and Netscape 8 + * @type {boolean} + */ +goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_; -goog.require('goog.userAgent'); + +/** + * Whether the user agent is Gecko. Gecko is the rendering engine used by + * Mozilla, Mozilla Firefox, Camino and many more. + * @type {boolean} + */ +goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_GECKO : + goog.userAgent.detectedGecko_; /** - * Constants for event names. - * @enum {string} + * Whether the user agent is WebKit. WebKit is the rendering engine that + * Safari, Android and others use. + * @type {boolean} */ -goog.events.EventType = { - // Mouse events - CLICK: 'click', - DBLCLICK: 'dblclick', - MOUSEDOWN: 'mousedown', - MOUSEUP: 'mouseup', - MOUSEOVER: 'mouseover', - MOUSEOUT: 'mouseout', - MOUSEMOVE: 'mousemove', - SELECTSTART: 'selectstart', // IE, Safari, Chrome +goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? + goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : + goog.userAgent.detectedWebkit_; - // Key events - KEYPRESS: 'keypress', - KEYDOWN: 'keydown', - KEYUP: 'keyup', - // Focus - BLUR: 'blur', - FOCUS: 'focus', - DEACTIVATE: 'deactivate', // IE only - // NOTE: The following two events are not stable in cross-browser usage. - // WebKit and Opera implement DOMFocusIn/Out. - // IE implements focusin/out. - // Gecko implements neither see bug at - // https://bugzilla.mozilla.org/show_bug.cgi?id=396927. - // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin: - // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html - // You can use FOCUS in Capture phase until implementations converge. - FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn', - FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut', +/** + * Whether the user agent is running on a mobile device. + * @type {boolean} + */ +goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT || + goog.userAgent.detectedMobile_; - // Forms - CHANGE: 'change', - SELECT: 'select', - SUBMIT: 'submit', - INPUT: 'input', - PROPERTYCHANGE: 'propertychange', // IE only - - // Drag and drop - DRAGSTART: 'dragstart', - DRAG: 'drag', - DRAGENTER: 'dragenter', - DRAGOVER: 'dragover', - DRAGLEAVE: 'dragleave', - DROP: 'drop', - DRAGEND: 'dragend', - - // WebKit touch events. - TOUCHSTART: 'touchstart', - TOUCHMOVE: 'touchmove', - TOUCHEND: 'touchend', - TOUCHCANCEL: 'touchcancel', - - // Misc - BEFOREUNLOAD: 'beforeunload', - CONTEXTMENU: 'contextmenu', - ERROR: 'error', - HELP: 'help', - LOAD: 'load', - LOSECAPTURE: 'losecapture', - READYSTATECHANGE: 'readystatechange', - RESIZE: 'resize', - SCROLL: 'scroll', - UNLOAD: 'unload', - - // HTML 5 History events - // See http://www.w3.org/TR/html5/history.html#event-definitions - HASHCHANGE: 'hashchange', - PAGEHIDE: 'pagehide', - PAGESHOW: 'pageshow', - POPSTATE: 'popstate', - - // Copy and Paste - // Support is limited. Make sure it works on your favorite browser - // before using. - // http://www.quirksmode.org/dom/events/cutcopypaste.html - COPY: 'copy', - PASTE: 'paste', - CUT: 'cut', - BEFORECOPY: 'beforecopy', - BEFORECUT: 'beforecut', - BEFOREPASTE: 'beforepaste', - - // HTML5 online/offline events. - // http://www.w3.org/TR/offline-webapps/#related - ONLINE: 'online', - OFFLINE: 'offline', - - // HTML 5 worker events - MESSAGE: 'message', - CONNECT: 'connect', - - // CSS transition events. Based on the browser support described at: - // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility - TRANSITIONEND: goog.userAgent.WEBKIT ? 'webkitTransitionEnd' : - (goog.userAgent.OPERA ? 'oTransitionEnd' : 'transitionend'), - - // IE specific events. - // See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx - MSGESTURECHANGE: 'MSGestureChange', - MSGESTUREEND: 'MSGestureEnd', - MSGESTUREHOLD: 'MSGestureHold', - MSGESTURESTART: 'MSGestureStart', - MSGESTURETAP: 'MSGestureTap', - MSGOTPOINTERCAPTURE: 'MSGotPointerCapture', - MSINERTIASTART: 'MSInertiaStart', - MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture', - MSPOINTERCANCEL: 'MSPointerCancel', - MSPOINTERDOWN: 'MSPointerDown', - MSPOINTERMOVE: 'MSPointerMove', - MSPOINTEROVER: 'MSPointerOver', - MSPOINTEROUT: 'MSPointerOut', - MSPOINTERUP: 'MSPointerUp', - - // Native IMEs/input tools events. - TEXTINPUT: 'textinput', - COMPOSITIONSTART: 'compositionstart', - COMPOSITIONUPDATE: 'compositionupdate', - COMPOSITIONEND: 'compositionend' -}; -} -if(!lt.util.load.provided_QMARK_('goog.events.BrowserEvent')) { -// Copyright 2005 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview A patched, standardized event object for browser events. - * - *
- * The patched event object contains the following members:
- * - type           {string}    Event type, e.g. 'click'
- * - timestamp      {Date}      A date object for when the event was fired
- * - target         {Object}    The element that actually triggered the event
- * - currentTarget  {Object}    The element the listener is attached to
- * - relatedTarget  {Object}    For mouseover and mouseout, the previous object
- * - offsetX        {number}    X-coordinate relative to target
- * - offsetY        {number}    Y-coordinate relative to target
- * - clientX        {number}    X-coordinate relative to viewport
- * - clientY        {number}    Y-coordinate relative to viewport
- * - screenX        {number}    X-coordinate relative to the edge of the screen
- * - screenY        {number}    Y-coordinate relative to the edge of the screen
- * - button         {number}    Mouse button. Use isButton() to test.
- * - keyCode        {number}    Key-code
- * - ctrlKey        {boolean}   Was ctrl key depressed
- * - altKey         {boolean}   Was alt key depressed
- * - shiftKey       {boolean}   Was shift key depressed
- * - metaKey        {boolean}   Was meta key depressed
- * - defaultPrevented {boolean} Whether the default action has been prevented
- * - state          {Object}    History state object
- *
- * NOTE: The keyCode member contains the raw browser keyCode. For normalized
- * key and character code use {@link goog.events.KeyHandler}.
- * 
- * - */ - -goog.provide('goog.events.BrowserEvent'); -goog.provide('goog.events.BrowserEvent.MouseButton'); - -goog.require('goog.events.BrowserFeature'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventType'); -goog.require('goog.reflect'); -goog.require('goog.userAgent'); +/** + * Used while transitioning code to use WEBKIT instead. + * @type {boolean} + * @deprecated Use {@link goog.userAgent.product.SAFARI} instead. + * TODO(nicksantos): Delete this from goog.userAgent. + */ +goog.userAgent.SAFARI = goog.userAgent.WEBKIT; /** - * Accepts a browser event object and creates a patched, cross browser event - * object. - * The content of this object will not be initialized if no event object is - * provided. If this is the case, init() needs to be invoked separately. - * @param {Event=} opt_e Browser event object. - * @param {EventTarget=} opt_currentTarget Current target for event. - * @constructor - * @extends {goog.events.Event} + * @return {string} the platform (operating system) the user agent is running + * on. Default to empty string because navigator.platform may not be defined + * (on Rhino, for example). + * @private */ -goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { - if (opt_e) { - this.init(opt_e, opt_currentTarget); - } +goog.userAgent.determinePlatform_ = function() { + var navigator = goog.userAgent.getNavigator(); + return navigator && navigator.platform || ''; }; -goog.inherits(goog.events.BrowserEvent, goog.events.Event); /** - * Normalized button constants for the mouse. - * @enum {number} + * The platform (operating system) the user agent is running on. Default to + * empty string because navigator.platform may not be defined (on Rhino, for + * example). + * @type {string} */ -goog.events.BrowserEvent.MouseButton = { - LEFT: 0, - MIDDLE: 1, - RIGHT: 2 -}; +goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); /** - * Static data for mapping mouse buttons. - * @type {Array.} + * @define {boolean} Whether the user agent is running on a Macintosh operating + * system. */ -goog.events.BrowserEvent.IEButtonMap = [ - 1, // LEFT - 4, // MIDDLE - 2 // RIGHT -]; +goog.define('goog.userAgent.ASSUME_MAC', false); /** - * Target that fired the event. - * @override - * @type {Node} + * @define {boolean} Whether the user agent is running on a Windows operating + * system. */ -goog.events.BrowserEvent.prototype.target = null; +goog.define('goog.userAgent.ASSUME_WINDOWS', false); /** - * Node that had the listener attached. - * @override - * @type {Node|undefined} + * @define {boolean} Whether the user agent is running on a Linux operating + * system. */ -goog.events.BrowserEvent.prototype.currentTarget; +goog.define('goog.userAgent.ASSUME_LINUX', false); /** - * For mouseover and mouseout events, the related object for the event. - * @type {Node} + * @define {boolean} Whether the user agent is running on a X11 windowing + * system. */ -goog.events.BrowserEvent.prototype.relatedTarget = null; +goog.define('goog.userAgent.ASSUME_X11', false); /** - * X-coordinate relative to target. - * @type {number} + * @define {boolean} Whether the user agent is running on Android. */ -goog.events.BrowserEvent.prototype.offsetX = 0; +goog.define('goog.userAgent.ASSUME_ANDROID', false); /** - * Y-coordinate relative to target. - * @type {number} + * @define {boolean} Whether the user agent is running on an iPhone. */ -goog.events.BrowserEvent.prototype.offsetY = 0; +goog.define('goog.userAgent.ASSUME_IPHONE', false); /** - * X-coordinate relative to the window. - * @type {number} + * @define {boolean} Whether the user agent is running on an iPad. */ -goog.events.BrowserEvent.prototype.clientX = 0; +goog.define('goog.userAgent.ASSUME_IPAD', false); /** - * Y-coordinate relative to the window. - * @type {number} + * @type {boolean} + * @private */ -goog.events.BrowserEvent.prototype.clientY = 0; +goog.userAgent.PLATFORM_KNOWN_ = + goog.userAgent.ASSUME_MAC || + goog.userAgent.ASSUME_WINDOWS || + goog.userAgent.ASSUME_LINUX || + goog.userAgent.ASSUME_X11 || + goog.userAgent.ASSUME_ANDROID || + goog.userAgent.ASSUME_IPHONE || + goog.userAgent.ASSUME_IPAD; /** - * X-coordinate relative to the monitor. - * @type {number} + * Initialize the goog.userAgent constants that define which platform the user + * agent is running on. + * @private */ -goog.events.BrowserEvent.prototype.screenX = 0; +goog.userAgent.initPlatform_ = function() { + /** + * Whether the user agent is running on a Macintosh operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM, + 'Mac'); + + /** + * Whether the user agent is running on a Windows operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedWindows_ = goog.string.contains( + goog.userAgent.PLATFORM, 'Win'); + /** + * Whether the user agent is running on a Linux operating system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM, + 'Linux'); -/** - * Y-coordinate relative to the monitor. - * @type {number} - */ -goog.events.BrowserEvent.prototype.screenY = 0; + /** + * Whether the user agent is running on a X11 windowing system. + * @type {boolean} + * @private + */ + goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() && + goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '', + 'X11'); + // Need user agent string for Android/IOS detection + var ua = goog.userAgent.getUserAgentString(); -/** - * Which mouse button was pressed. - * @type {number} - */ -goog.events.BrowserEvent.prototype.button = 0; + /** + * Whether the user agent is running on Android. + * @type {boolean} + * @private + */ + goog.userAgent.detectedAndroid_ = !!ua && + goog.string.contains(ua, 'Android'); + /** + * Whether the user agent is running on an iPhone. + * @type {boolean} + * @private + */ + goog.userAgent.detectedIPhone_ = !!ua && goog.string.contains(ua, 'iPhone'); -/** - * Keycode of key press. - * @type {number} - */ -goog.events.BrowserEvent.prototype.keyCode = 0; + /** + * Whether the user agent is running on an iPad. + * @type {boolean} + * @private + */ + goog.userAgent.detectedIPad_ = !!ua && goog.string.contains(ua, 'iPad'); +}; -/** - * Keycode of key press. - * @type {number} - */ -goog.events.BrowserEvent.prototype.charCode = 0; +if (!goog.userAgent.PLATFORM_KNOWN_) { + goog.userAgent.initPlatform_(); +} /** - * Whether control was pressed at time of event. + * Whether the user agent is running on a Macintosh operating system. * @type {boolean} */ -goog.events.BrowserEvent.prototype.ctrlKey = false; +goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_; /** - * Whether alt was pressed at time of event. + * Whether the user agent is running on a Windows operating system. * @type {boolean} */ -goog.events.BrowserEvent.prototype.altKey = false; +goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_; /** - * Whether shift was pressed at time of event. + * Whether the user agent is running on a Linux operating system. * @type {boolean} */ -goog.events.BrowserEvent.prototype.shiftKey = false; +goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_; /** - * Whether the meta key was pressed at time of event. + * Whether the user agent is running on a X11 windowing system. * @type {boolean} */ -goog.events.BrowserEvent.prototype.metaKey = false; +goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_; /** - * History state object, only set for PopState events where it's a copy of the - * state object provided to pushState or replaceState. - * @type {Object} + * Whether the user agent is running on Android. + * @type {boolean} */ -goog.events.BrowserEvent.prototype.state; +goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_; /** - * Whether the default platform modifier key was pressed at time of event. - * (This is control for all platforms except Mac, where it's Meta. + * Whether the user agent is running on an iPhone. * @type {boolean} */ -goog.events.BrowserEvent.prototype.platformModifierKey = false; +goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_; /** - * The browser event object. - * @type {Event} - * @private + * Whether the user agent is running on an iPad. + * @type {boolean} */ -goog.events.BrowserEvent.prototype.event_ = null; +goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ? + goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_; /** - * Accepts a browser event object and creates a patched, cross browser event - * object. - * @param {Event} e Browser event object. - * @param {EventTarget=} opt_currentTarget Current target for event. + * @return {string} The string that describes the version number of the user + * agent. + * @private */ -goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) { - var type = this.type = e.type; - goog.events.Event.call(this, type); - - // TODO(nicksantos): Change this.target to type EventTarget. - this.target = /** @type {Node} */ (e.target) || e.srcElement; +goog.userAgent.determineVersion_ = function() { + // All browsers have different ways to detect the version and they all have + // different naming schemes. - // TODO(nicksantos): Change this.currentTarget to type EventTarget. - this.currentTarget = /** @type {Node} */ (opt_currentTarget); + // version is a string rather than a number because it may contain 'b', 'a', + // and so on. + var version = '', re; - var relatedTarget = /** @type {Node} */ (e.relatedTarget); - if (relatedTarget) { - // There's a bug in FireFox where sometimes, relatedTarget will be a - // chrome element, and accessing any property of it will get a permission - // denied exception. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=497780 + if (goog.userAgent.OPERA && goog.global['opera']) { + var operaVersion = goog.global['opera'].version; + version = typeof operaVersion == 'function' ? operaVersion() : operaVersion; + } else { if (goog.userAgent.GECKO) { - if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) { - relatedTarget = null; - } + re = /rv\:([^\);]+)(\)|;)/; + } else if (goog.userAgent.IE) { + re = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/; + } else if (goog.userAgent.WEBKIT) { + // WebKit/125.4 + re = /WebKit\/(\S+)/; + } + if (re) { + var arr = re.exec(goog.userAgent.getUserAgentString()); + version = arr ? arr[1] : ''; } - // TODO(arv): Use goog.events.EventType when it has been refactored into its - // own file. - } else if (type == goog.events.EventType.MOUSEOVER) { - relatedTarget = e.fromElement; - } else if (type == goog.events.EventType.MOUSEOUT) { - relatedTarget = e.toElement; } - - this.relatedTarget = relatedTarget; - - // Webkit emits a lame warning whenever layerX/layerY is accessed. - // http://code.google.com/p/chromium/issues/detail?id=101733 - this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ? - e.offsetX : e.layerX; - this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ? - e.offsetY : e.layerY; - - this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; - this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; - this.screenX = e.screenX || 0; - this.screenY = e.screenY || 0; - - this.button = e.button; - - this.keyCode = e.keyCode || 0; - this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0); - this.ctrlKey = e.ctrlKey; - this.altKey = e.altKey; - this.shiftKey = e.shiftKey; - this.metaKey = e.metaKey; - this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; - this.state = e.state; - this.event_ = e; - if (e.defaultPrevented) { - this.preventDefault(); + if (goog.userAgent.IE) { + // IE9 can be in document mode 9 but be reporting an inconsistent user agent + // version. If it is identifying as a version lower than 9 we take the + // documentMode as the version instead. IE8 has similar behavior. + // It is recommended to set the X-UA-Compatible header to ensure that IE9 + // uses documentMode 9. + var docMode = goog.userAgent.getDocumentMode_(); + if (docMode > parseFloat(version)) { + return String(docMode); + } } - delete this.propagationStopped_; + return version; }; /** - * Tests to see which button was pressed during the event. This is really only - * useful in IE and Gecko browsers. And in IE, it's only useful for - * mousedown/mouseup events, because click only fires for the left mouse button. - * - * Safari 2 only reports the left button being clicked, and uses the value '1' - * instead of 0. Opera only reports a mousedown event for the middle button, and - * no mouse events for the right button. Opera has default behavior for left and - * middle click that can only be overridden via a configuration setting. - * - * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html. - * - * @param {goog.events.BrowserEvent.MouseButton} button The button - * to test for. - * @return {boolean} True if button was pressed. + * @return {number|undefined} Returns the document mode (for testing). + * @private */ -goog.events.BrowserEvent.prototype.isButton = function(button) { - if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) { - if (this.type == 'click') { - return button == goog.events.BrowserEvent.MouseButton.LEFT; - } else { - return !!(this.event_.button & - goog.events.BrowserEvent.IEButtonMap[button]); - } - } else { - return this.event_.button == button; - } +goog.userAgent.getDocumentMode_ = function() { + // NOTE(user): goog.userAgent may be used in context where there is no DOM. + var doc = goog.global['document']; + return doc ? doc['documentMode'] : undefined; }; /** - * Whether this has an "action"-producing mouse button. + * The version of the user agent. This is a string because it might contain + * 'b' (as in beta) as well as multiple dots. + * @type {string} + */ +goog.userAgent.VERSION = goog.userAgent.determineVersion_(); + + +/** + * Compares two version numbers. * - * By definition, this includes left-click on windows/linux, and left-click - * without the ctrl key on Macs. + * @param {string} v1 Version of first item. + * @param {string} v2 Version of second item. * - * @return {boolean} The result. + * @return {number} 1 if first argument is higher + * 0 if arguments are equal + * -1 if second argument is higher. + * @deprecated Use goog.string.compareVersions. */ -goog.events.BrowserEvent.prototype.isMouseActionButton = function() { - // Webkit does not ctrl+click to be a right-click, so we - // normalize it to behave like Gecko and Opera. - return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) && - !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey); +goog.userAgent.compare = function(v1, v2) { + return goog.string.compareVersions(v1, v2); }; /** - * @override + * Cache for {@link goog.userAgent.isVersionOrHigher}. + * Calls to compareVersions are surprisingly expensive and, as a browser's + * version number is unlikely to change during a session, we cache the results. + * @const + * @private */ -goog.events.BrowserEvent.prototype.stopPropagation = function() { - goog.events.BrowserEvent.superClass_.stopPropagation.call(this); - if (this.event_.stopPropagation) { - this.event_.stopPropagation(); - } else { - this.event_.cancelBubble = true; - } -}; +goog.userAgent.isVersionOrHigherCache_ = {}; /** - * @override + * Whether the user agent version is higher or the same as the given version. + * NOTE: When checking the version numbers for Firefox and Safari, be sure to + * use the engine's version, not the browser's version number. For example, + * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11. + * Opera and Internet Explorer versions match the product release number.
+ * @see + * Webkit + * @see Gecko + * + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. */ -goog.events.BrowserEvent.prototype.preventDefault = function() { - goog.events.BrowserEvent.superClass_.preventDefault.call(this); - var be = this.event_; - if (!be.preventDefault) { - be.returnValue = false; - if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) { - /** @preserveTry */ - try { - // Most keys can be prevented using returnValue. Some special keys - // require setting the keyCode to -1 as well: - // - // In IE7: - // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6) - // - // In IE8: - // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event) - // - // We therefore do this for all function keys as well as when Ctrl key - // is pressed. - var VK_F1 = 112; - var VK_F12 = 123; - if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) { - be.keyCode = -1; - } - } catch (ex) { - // IE throws an 'access denied' exception when trying to change - // keyCode in some situations (e.g. srcElement is input[type=file], - // or srcElement is an anchor tag rewritten by parent's innerHTML). - // Do nothing in this case. - } - } - } else { - be.preventDefault(); - } +goog.userAgent.isVersionOrHigher = function(version) { + return goog.userAgent.ASSUME_ANY_VERSION || + goog.userAgent.isVersionOrHigherCache_[version] || + (goog.userAgent.isVersionOrHigherCache_[version] = + goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0); }; /** - * @return {Event} The underlying browser event object. + * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}. + * @param {string|number} version The version to check. + * @return {boolean} Whether the user agent version is higher or the same as + * the given version. + * @deprecated Use goog.userAgent.isVersionOrHigher(). */ -goog.events.BrowserEvent.prototype.getBrowserEvent = function() { - return this.event_; -}; +goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher; -/** @override */ -goog.events.BrowserEvent.prototype.disposeInternal = function() { +/** + * Whether the IE effective document mode is higher or the same as the given + * document mode version. + * NOTE: Only for IE, return false for another browser. + * + * @param {number} documentMode The document mode version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + */ +goog.userAgent.isDocumentModeOrHigher = function(documentMode) { + return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode; }; -} -if(!lt.util.load.provided_QMARK_('goog.object')) { -// Copyright 2006 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. + /** - * @fileoverview Utilities for manipulating objects/maps/hashes. + * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}. + * @param {number} version The version to check. + * @return {boolean} Whether the IE effective document mode is higher or the + * same as the given version. + * @deprecated Use goog.userAgent.isDocumentModeOrHigher(). */ - -goog.provide('goog.object'); +goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher; /** - * Calls a function for each element in an object/map/hash. - * - * @param {Object.} obj The object over which to iterate. - * @param {function(this:T,V,?,Object.):?} f The function to call - * for every element. This function takes 3 arguments (the element, the - * index and the object) and the return value is ignored. - * @param {T=} opt_obj This is used as the 'this' object within f. - * @template T,K,V + * For IE version < 7, documentMode is undefined, so attempt to use the + * CSS1Compat property to see if we are in standards mode. If we are in + * standards mode, treat the browser version as the document mode. Otherwise, + * IE is emulating version 5. + * @type {number|undefined} + * @const */ -goog.object.forEach = function(obj, f, opt_obj) { - for (var key in obj) { - f.call(opt_obj, obj[key], key, obj); +goog.userAgent.DOCUMENT_MODE = (function() { + var doc = goog.global['document']; + if (!doc || !goog.userAgent.IE) { + return undefined; } -}; - + var mode = goog.userAgent.getDocumentMode_(); + return mode || (doc['compatMode'] == 'CSS1Compat' ? + parseInt(goog.userAgent.VERSION, 10) : 5); +})(); +} +if(!lt.util.load.provided_QMARK_('goog.events.BrowserFeature')) { +// 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. /** - * Calls a function for each element in an object/map/hash. If that call returns - * true, adds the element to a new object. + * @fileoverview Browser capability checks for the events package. * - * @param {Object.} obj The object over which to iterate. - * @param {function(this:T,V,?,Object.):boolean} f The function to call - * for every element. This - * function takes 3 arguments (the element, the index and the object) - * and should return a boolean. If the return value is true the - * element is added to the result object. If it is false the - * element is not included. - * @param {T=} opt_obj This is used as the 'this' object within f. - * @return {!Object.} a new object in which only elements that passed the - * test are present. - * @template T,K,V */ -goog.object.filter = function(obj, f, opt_obj) { - var res = {}; - for (var key in obj) { - if (f.call(opt_obj, obj[key], key, obj)) { - res[key] = obj[key]; - } - } - return res; -}; -/** - * For every element in an object/map/hash calls a function and inserts the - * result into a new object. - * - * @param {Object.} obj The object over which to iterate. - * @param {function(this:T,V,?,Object.):R} f The function to call - * for every element. This function - * takes 3 arguments (the element, the index and the object) - * and should return something. The result will be inserted - * into a new object. - * @param {T=} opt_obj This is used as the 'this' object within f. - * @return {!Object.} a new object with the results from f. - * @template T,K,V,R - */ -goog.object.map = function(obj, f, opt_obj) { - var res = {}; - for (var key in obj) { - res[key] = f.call(opt_obj, obj[key], key, obj); - } - return res; -}; +goog.provide('goog.events.BrowserFeature'); + +goog.require('goog.userAgent'); /** - * Calls a function for each element in an object/map/hash. If any - * call returns true, returns true (without checking the rest). If - * all calls return false, returns false. - * - * @param {Object.} obj The object to check. - * @param {function(this:T,V,?,Object.):boolean} f The function to - * call for every element. This function - * takes 3 arguments (the element, the index and the object) and should - * return a boolean. - * @param {T=} opt_obj This is used as the 'this' object within f. - * @return {boolean} true if any element passes the test. - * @template T,K,V + * Enum of browser capabilities. + * @enum {boolean} */ -goog.object.some = function(obj, f, opt_obj) { - for (var key in obj) { - if (f.call(opt_obj, obj[key], key, obj)) { - return true; - } - } - return false; -}; +goog.events.BrowserFeature = { + /** + * Whether the button attribute of the event is W3C compliant. False in + * Internet Explorer prior to version 9; document-version dependent. + */ + HAS_W3C_BUTTON: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), + /** + * Whether the browser supports full W3C event model. + */ + HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE || + goog.userAgent.isDocumentModeOrHigher(9), -/** - * Calls a function for each element in an object/map/hash. If - * all calls return true, returns true. If any call returns false, returns - * false at this point and does not continue to check the remaining elements. - * - * @param {Object.} obj The object to check. - * @param {?function(this:T,V,?,Object.):boolean} f The function to - * call for every element. This function - * takes 3 arguments (the element, the index and the object) and should - * return a boolean. - * @param {T=} opt_obj This is used as the 'this' object within f. - * @return {boolean} false if any element fails the test. - * @template T,K,V - */ -goog.object.every = function(obj, f, opt_obj) { - for (var key in obj) { - if (!f.call(opt_obj, obj[key], key, obj)) { - return false; - } - } - return true; -}; + /** + * To prevent default in IE7-8 for certain keydown events we need set the + * keyCode to -1. + */ + SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE && + !goog.userAgent.isVersionOrHigher('9'), + /** + * Whether the {@code navigator.onLine} property is supported. + */ + HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT || + goog.userAgent.isVersionOrHigher('528'), -/** - * Returns the number of key-value pairs in the object map. - * - * @param {Object} obj The object for which to get the number of key-value - * pairs. - * @return {number} The number of key-value pairs in the object map. - */ -goog.object.getCount = function(obj) { - // JS1.5 has __count__ but it has been deprecated so it raises a warning... - // in other words do not use. Also __count__ only includes the fields on the - // actual object and not in the prototype chain. - var rv = 0; - for (var key in obj) { - rv++; - } - return rv; -}; + /** + * Whether HTML5 network online/offline events are supported. + */ + HAS_HTML5_NETWORK_EVENT_SUPPORT: + goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') || + goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') || + goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'), + /** + * Whether HTML5 network events fire on document.body, or otherwise the + * window. + */ + HTML5_NETWORK_EVENTS_FIRE_ON_BODY: + goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') || + goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'), -/** - * Returns one key from the object map, if any exists. - * For map literals the returned key will be the first one in most of the - * browsers (a know exception is Konqueror). - * - * @param {Object} obj The object to pick a key from. - * @return {string|undefined} The key or undefined if the object is empty. - */ -goog.object.getAnyKey = function(obj) { - for (var key in obj) { - return key; - } + /** + * Whether touch is enabled in the browser. + */ + TOUCH_ENABLED: + ('ontouchstart' in goog.global || + !!(goog.global['document'] && + document.documentElement && + 'ontouchstart' in document.documentElement) || + // IE10 uses non-standard touch events, so it has a different check. + !!(goog.global['navigator'] && + goog.global['navigator']['msMaxTouchPoints'])) }; - +} +if(!lt.util.load.provided_QMARK_('goog.events.EventType')) { +// 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. /** - * Returns one value from the object map, if any exists. - * For map literals the returned value will be the first one in most of the - * browsers (a know exception is Konqueror). + * @fileoverview Event Types. * - * @param {Object.} obj The object to pick a value from. - * @return {V|undefined} The value or undefined if the object is empty. - * @template K,V + * @author arv@google.com (Erik Arvidsson) + * @author mirkov@google.com (Mirko Visontai) */ -goog.object.getAnyValue = function(obj) { - for (var key in obj) { - return obj[key]; - } -}; -/** - * Whether the object/hash/map contains the given object as a value. - * An alias for goog.object.containsValue(obj, val). - * - * @param {Object.} obj The object in which to look for val. - * @param {V} val The object for which to check. - * @return {boolean} true if val is present. - * @template K,V - */ -goog.object.contains = function(obj, val) { - return goog.object.containsValue(obj, val); -}; +goog.provide('goog.events.EventType'); + +goog.require('goog.userAgent'); /** - * Returns the values of the object/map/hash. - * - * @param {Object.} obj The object from which to get the values. - * @return {!Array.} The values in the object/map/hash. - * @template K,V + * Returns a prefixed event name for the current browser. + * @param {string} eventName The name of the event. + * @return {string} The prefixed event name. + * @suppress {missingRequire|missingProvide} + * @private */ -goog.object.getValues = function(obj) { - var res = []; - var i = 0; - for (var key in obj) { - res[i++] = obj[key]; - } - return res; +goog.events.getVendorPrefixedName_ = function(eventName) { + return goog.userAgent.WEBKIT ? 'webkit' + eventName : + (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() : + eventName.toLowerCase()); }; /** - * Returns the keys of the object/map/hash. - * - * @param {Object} obj The object from which to get the keys. - * @return {!Array.} Array of property keys. + * Constants for event names. + * @enum {string} */ -goog.object.getKeys = function(obj) { - var res = []; - var i = 0; - for (var key in obj) { - res[i++] = key; - } - return res; -}; +goog.events.EventType = { + // Mouse events + CLICK: 'click', + DBLCLICK: 'dblclick', + MOUSEDOWN: 'mousedown', + MOUSEUP: 'mouseup', + MOUSEOVER: 'mouseover', + MOUSEOUT: 'mouseout', + MOUSEMOVE: 'mousemove', + MOUSEENTER: 'mouseenter', + MOUSELEAVE: 'mouseleave', + // Select start is non-standard. + // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx. + SELECTSTART: 'selectstart', // IE, Safari, Chrome + // Key events + KEYPRESS: 'keypress', + KEYDOWN: 'keydown', + KEYUP: 'keyup', -/** - * Get a value from an object multiple levels deep. This is useful for - * pulling values from deeply nested objects, such as JSON responses. - * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3) - * - * @param {!Object} obj An object to get the value from. Can be array-like. - * @param {...(string|number|!Array.)} var_args A number of keys - * (as strings, or numbers, for array-like objects). Can also be - * specified as a single array of keys. - * @return {*} The resulting value. If, at any point, the value for a key - * is undefined, returns undefined. - */ -goog.object.getValueByKeys = function(obj, var_args) { - var isArrayLike = goog.isArrayLike(var_args); - var keys = isArrayLike ? var_args : arguments; + // Focus + BLUR: 'blur', + FOCUS: 'focus', + DEACTIVATE: 'deactivate', // IE only + // NOTE: The following two events are not stable in cross-browser usage. + // WebKit and Opera implement DOMFocusIn/Out. + // IE implements focusin/out. + // Gecko implements neither see bug at + // https://bugzilla.mozilla.org/show_bug.cgi?id=396927. + // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin: + // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html + // You can use FOCUS in Capture phase until implementations converge. + FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn', + FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut', - // Start with the 2nd parameter for the variable parameters syntax. - for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { - obj = obj[keys[i]]; - if (!goog.isDef(obj)) { - break; - } - } + // Forms + CHANGE: 'change', + SELECT: 'select', + SUBMIT: 'submit', + INPUT: 'input', + PROPERTYCHANGE: 'propertychange', // IE only - return obj; -}; + // Drag and drop + DRAGSTART: 'dragstart', + DRAG: 'drag', + DRAGENTER: 'dragenter', + DRAGOVER: 'dragover', + DRAGLEAVE: 'dragleave', + DROP: 'drop', + DRAGEND: 'dragend', + // WebKit touch events. + TOUCHSTART: 'touchstart', + TOUCHMOVE: 'touchmove', + TOUCHEND: 'touchend', + TOUCHCANCEL: 'touchcancel', -/** - * Whether the object/map/hash contains the given key. - * - * @param {Object} obj The object in which to look for key. - * @param {*} key The key for which to check. - * @return {boolean} true If the map contains the key. - */ -goog.object.containsKey = function(obj, key) { - return key in obj; -}; + // Misc + BEFOREUNLOAD: 'beforeunload', + CONSOLEMESSAGE: 'consolemessage', + CONTEXTMENU: 'contextmenu', + DOMCONTENTLOADED: 'DOMContentLoaded', + ERROR: 'error', + HELP: 'help', + LOAD: 'load', + LOSECAPTURE: 'losecapture', + ORIENTATIONCHANGE: 'orientationchange', + READYSTATECHANGE: 'readystatechange', + RESIZE: 'resize', + SCROLL: 'scroll', + UNLOAD: 'unload', + // HTML 5 History events + // See http://www.w3.org/TR/html5/history.html#event-definitions + HASHCHANGE: 'hashchange', + PAGEHIDE: 'pagehide', + PAGESHOW: 'pageshow', + POPSTATE: 'popstate', -/** - * Whether the object/map/hash contains the given value. This is O(n). - * - * @param {Object.} obj The object in which to look for val. - * @param {V} val The value for which to check. - * @return {boolean} true If the map contains the value. - * @template K,V - */ -goog.object.containsValue = function(obj, val) { - for (var key in obj) { - if (obj[key] == val) { - return true; - } - } - return false; -}; + // Copy and Paste + // Support is limited. Make sure it works on your favorite browser + // before using. + // http://www.quirksmode.org/dom/events/cutcopypaste.html + COPY: 'copy', + PASTE: 'paste', + CUT: 'cut', + BEFORECOPY: 'beforecopy', + BEFORECUT: 'beforecut', + BEFOREPASTE: 'beforepaste', + // HTML5 online/offline events. + // http://www.w3.org/TR/offline-webapps/#related + ONLINE: 'online', + OFFLINE: 'offline', -/** - * Searches an object for an element that satisfies the given condition and - * returns its key. - * @param {Object.} obj The object to search in. - * @param {function(this:T,V,string,Object.):boolean} f The - * function to call for every element. Takes 3 arguments (the value, - * the key and the object) and should return a boolean. - * @param {T=} opt_this An optional "this" context for the function. - * @return {string|undefined} The key of an element for which the function - * returns true or undefined if no such element is found. - * @template T,K,V - */ -goog.object.findKey = function(obj, f, opt_this) { - for (var key in obj) { - if (f.call(opt_this, obj[key], key, obj)) { - return key; - } - } - return undefined; -}; + // HTML 5 worker events + MESSAGE: 'message', + CONNECT: 'connect', + // CSS animation events. + /** @suppress {missingRequire} */ + ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'), + /** @suppress {missingRequire} */ + ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'), + /** @suppress {missingRequire} */ + ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'), -/** - * Searches an object for an element that satisfies the given condition and - * returns its value. - * @param {Object.} obj The object to search in. - * @param {function(this:T,V,string,Object.):boolean} f The function - * to call for every element. Takes 3 arguments (the value, the key - * and the object) and should return a boolean. - * @param {T=} opt_this An optional "this" context for the function. - * @return {V} The value of an element for which the function returns true or - * undefined if no such element is found. - * @template T,K,V - */ -goog.object.findValue = function(obj, f, opt_this) { - var key = goog.object.findKey(obj, f, opt_this); - return key && obj[key]; -}; + // CSS transition events. Based on the browser support described at: + // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility + /** @suppress {missingRequire} */ + TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'), + + // W3C Pointer Events + // http://www.w3.org/TR/pointerevents/ + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + POINTERCANCEL: 'pointercancel', + POINTERMOVE: 'pointermove', + POINTEROVER: 'pointerover', + POINTEROUT: 'pointerout', + POINTERENTER: 'pointerenter', + POINTERLEAVE: 'pointerleave', + GOTPOINTERCAPTURE: 'gotpointercapture', + LOSTPOINTERCAPTURE: 'lostpointercapture', + // IE specific events. + // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx + // Note: these events will be supplanted in IE11. + MSGESTURECHANGE: 'MSGestureChange', + MSGESTUREEND: 'MSGestureEnd', + MSGESTUREHOLD: 'MSGestureHold', + MSGESTURESTART: 'MSGestureStart', + MSGESTURETAP: 'MSGestureTap', + MSGOTPOINTERCAPTURE: 'MSGotPointerCapture', + MSINERTIASTART: 'MSInertiaStart', + MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture', + MSPOINTERCANCEL: 'MSPointerCancel', + MSPOINTERDOWN: 'MSPointerDown', + MSPOINTERENTER: 'MSPointerEnter', + MSPOINTERHOVER: 'MSPointerHover', + MSPOINTERLEAVE: 'MSPointerLeave', + MSPOINTERMOVE: 'MSPointerMove', + MSPOINTEROUT: 'MSPointerOut', + MSPOINTEROVER: 'MSPointerOver', + MSPOINTERUP: 'MSPointerUp', -/** - * Whether the object/map/hash is empty. - * - * @param {Object} obj The object to test. - * @return {boolean} true if obj is empty. - */ -goog.object.isEmpty = function(obj) { - for (var key in obj) { - return false; - } - return true; + // Native IMEs/input tools events. + TEXTINPUT: 'textinput', + COMPOSITIONSTART: 'compositionstart', + COMPOSITIONUPDATE: 'compositionupdate', + COMPOSITIONEND: 'compositionend', + + // Webview tag events + // See http://developer.chrome.com/dev/apps/webview_tag.html + EXIT: 'exit', + LOADABORT: 'loadabort', + LOADCOMMIT: 'loadcommit', + LOADREDIRECT: 'loadredirect', + LOADSTART: 'loadstart', + LOADSTOP: 'loadstop', + RESPONSIVE: 'responsive', + SIZECHANGED: 'sizechanged', + UNRESPONSIVE: 'unresponsive', + + // HTML5 Page Visibility API. See details at + // {@code goog.labs.dom.PageVisibilityMonitor}. + VISIBILITYCHANGE: 'visibilitychange', + + // LocalStorage event. + STORAGE: 'storage' }; - +} +if(!lt.util.load.provided_QMARK_('goog.events.BrowserEvent')) { +// 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. /** - * Removes all key value pairs from the object/map/hash. + * @fileoverview A patched, standardized event object for browser events. + * + *
+ * The patched event object contains the following members:
+ * - type           {string}    Event type, e.g. 'click'
+ * - timestamp      {Date}      A date object for when the event was fired
+ * - target         {Object}    The element that actually triggered the event
+ * - currentTarget  {Object}    The element the listener is attached to
+ * - relatedTarget  {Object}    For mouseover and mouseout, the previous object
+ * - offsetX        {number}    X-coordinate relative to target
+ * - offsetY        {number}    Y-coordinate relative to target
+ * - clientX        {number}    X-coordinate relative to viewport
+ * - clientY        {number}    Y-coordinate relative to viewport
+ * - screenX        {number}    X-coordinate relative to the edge of the screen
+ * - screenY        {number}    Y-coordinate relative to the edge of the screen
+ * - button         {number}    Mouse button. Use isButton() to test.
+ * - keyCode        {number}    Key-code
+ * - ctrlKey        {boolean}   Was ctrl key depressed
+ * - altKey         {boolean}   Was alt key depressed
+ * - shiftKey       {boolean}   Was shift key depressed
+ * - metaKey        {boolean}   Was meta key depressed
+ * - defaultPrevented {boolean} Whether the default action has been prevented
+ * - state          {Object}    History state object
+ *
+ * NOTE: The keyCode member contains the raw browser keyCode. For normalized
+ * key and character code use {@link goog.events.KeyHandler}.
+ * 
* - * @param {Object} obj The object to clear. */ -goog.object.clear = function(obj) { - for (var i in obj) { - delete obj[i]; - } -}; +goog.provide('goog.events.BrowserEvent'); +goog.provide('goog.events.BrowserEvent.MouseButton'); + +goog.require('goog.events.BrowserFeature'); +goog.require('goog.events.Event'); +goog.require('goog.events.EventType'); +goog.require('goog.reflect'); +goog.require('goog.userAgent'); -/** - * Removes a key-value pair based on the key. - * - * @param {Object} obj The object from which to remove the key. - * @param {*} key The key to remove. - * @return {boolean} Whether an element was removed. - */ -goog.object.remove = function(obj, key) { - var rv; - if ((rv = key in obj)) { - delete obj[key]; - } - return rv; -}; /** - * Adds a key-value pair to the object. Throws an exception if the key is - * already in use. Use set if you want to change an existing pair. - * - * @param {Object.} obj The object to which to add the key-value pair. - * @param {string} key The key to add. - * @param {V} val The value to add. - * @template K,V + * Accepts a browser event object and creates a patched, cross browser event + * object. + * The content of this object will not be initialized if no event object is + * provided. If this is the case, init() needs to be invoked separately. + * @param {Event=} opt_e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. + * @constructor + * @extends {goog.events.Event} */ -goog.object.add = function(obj, key, val) { - if (key in obj) { - throw Error('The object already contains the key "' + key + '"'); - } - goog.object.set(obj, key, val); -}; +goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { + goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : ''); + /** + * Target that fired the event. + * @override + * @type {Node} + */ + this.target = null; -/** - * Returns the value for the given key. - * - * @param {Object.} obj The object from which to get the value. - * @param {string} key The key for which to get the value. - * @param {R=} opt_val The value to return if no item is found for the given - * key (default is undefined). - * @return {V|R|undefined} The value for the given key. - * @template K,V,R - */ -goog.object.get = function(obj, key, opt_val) { - if (key in obj) { - return obj[key]; - } - return opt_val; -}; + /** + * Node that had the listener attached. + * @override + * @type {Node|undefined} + */ + this.currentTarget = null; + /** + * For mouseover and mouseout events, the related object for the event. + * @type {Node} + */ + this.relatedTarget = null; -/** - * Adds a key-value pair to the object/map/hash. - * - * @param {Object.} obj The object to which to add the key-value pair. - * @param {string} key The key to add. - * @param {K} value The value to add. - * @template K,V - */ -goog.object.set = function(obj, key, value) { - obj[key] = value; -}; + /** + * X-coordinate relative to target. + * @type {number} + */ + this.offsetX = 0; + /** + * Y-coordinate relative to target. + * @type {number} + */ + this.offsetY = 0; -/** - * Adds a key-value pair to the object/map/hash if it doesn't exist yet. - * - * @param {Object.} obj The object to which to add the key-value pair. - * @param {string} key The key to add. - * @param {V} value The value to add if the key wasn't present. - * @return {V} The value of the entry at the end of the function. - * @template K,V - */ -goog.object.setIfUndefined = function(obj, key, value) { - return key in obj ? obj[key] : (obj[key] = value); -}; + /** + * X-coordinate relative to the window. + * @type {number} + */ + this.clientX = 0; + /** + * Y-coordinate relative to the window. + * @type {number} + */ + this.clientY = 0; -/** - * Does a flat clone of the object. - * - * @param {Object.} obj Object to clone. - * @return {!Object.} Clone of the input object. - * @template K,V - */ -goog.object.clone = function(obj) { - // We cannot use the prototype trick because a lot of methods depend on where - // the actual key is set. + /** + * X-coordinate relative to the monitor. + * @type {number} + */ + this.screenX = 0; - var res = {}; - for (var key in obj) { - res[key] = obj[key]; - } - return res; - // We could also use goog.mixin but I wanted this to be independent from that. -}; + /** + * Y-coordinate relative to the monitor. + * @type {number} + */ + this.screenY = 0; + /** + * Which mouse button was pressed. + * @type {number} + */ + this.button = 0; -/** - * Clones a value. The input may be an Object, Array, or basic type. Objects and - * arrays will be cloned recursively. - * - * WARNINGS: - * goog.object.unsafeClone does not detect reference loops. Objects - * that refer to themselves will cause infinite recursion. - * - * goog.object.unsafeClone is unaware of unique identifiers, and - * copies UIDs created by getUid into cloned results. - * - * @param {*} obj The value to clone. - * @return {*} A clone of the input value. - */ -goog.object.unsafeClone = function(obj) { - var type = goog.typeOf(obj); - if (type == 'object' || type == 'array') { - if (obj.clone) { - return obj.clone(); - } - var clone = type == 'array' ? [] : {}; - for (var key in obj) { - clone[key] = goog.object.unsafeClone(obj[key]); - } - return clone; - } + /** + * Keycode of key press. + * @type {number} + */ + this.keyCode = 0; - return obj; -}; + /** + * Keycode of key press. + * @type {number} + */ + this.charCode = 0; + /** + * Whether control was pressed at time of event. + * @type {boolean} + */ + this.ctrlKey = false; -/** - * Returns a new object in which all the keys and values are interchanged - * (keys become values and values become keys). If multiple keys map to the - * same value, the chosen transposed value is implementation-dependent. - * - * @param {Object} obj The object to transpose. - * @return {!Object} The transposed object. - */ -goog.object.transpose = function(obj) { - var transposed = {}; - for (var key in obj) { - transposed[obj[key]] = key; + /** + * Whether alt was pressed at time of event. + * @type {boolean} + */ + this.altKey = false; + + /** + * Whether shift was pressed at time of event. + * @type {boolean} + */ + this.shiftKey = false; + + /** + * Whether the meta key was pressed at time of event. + * @type {boolean} + */ + this.metaKey = false; + + /** + * History state object, only set for PopState events where it's a copy of the + * state object provided to pushState or replaceState. + * @type {Object} + */ + this.state = null; + + /** + * Whether the default platform modifier key was pressed at time of event. + * (This is control for all platforms except Mac, where it's Meta.) + * @type {boolean} + */ + this.platformModifierKey = false; + + /** + * The browser event object. + * @private {Event} + */ + this.event_ = null; + + if (opt_e) { + this.init(opt_e, opt_currentTarget); } - return transposed; }; +goog.inherits(goog.events.BrowserEvent, goog.events.Event); /** - * The names of the fields that are defined on Object.prototype. - * @type {Array.} - * @private + * Normalized button constants for the mouse. + * @enum {number} */ -goog.object.PROTOTYPE_FIELDS_ = [ - 'constructor', - 'hasOwnProperty', - 'isPrototypeOf', - 'propertyIsEnumerable', - 'toLocaleString', - 'toString', - 'valueOf' +goog.events.BrowserEvent.MouseButton = { + LEFT: 0, + MIDDLE: 1, + RIGHT: 2 +}; + + +/** + * Static data for mapping mouse buttons. + * @type {!Array.} + */ +goog.events.BrowserEvent.IEButtonMap = [ + 1, // LEFT + 4, // MIDDLE + 2 // RIGHT ]; /** - * Extends an object with another object. - * This operates 'in-place'; it does not create a new Object. - * - * Example: - * var o = {}; - * goog.object.extend(o, {a: 0, b: 1}); - * o; // {a: 0, b: 1} - * goog.object.extend(o, {c: 2}); - * o; // {a: 0, b: 1, c: 2} - * - * @param {Object} target The object to modify. - * @param {...Object} var_args The objects from which values will be copied. + * Accepts a browser event object and creates a patched, cross browser event + * object. + * @param {Event} e Browser event object. + * @param {EventTarget=} opt_currentTarget Current target for event. */ -goog.object.extend = function(target, var_args) { - var key, source; - for (var i = 1; i < arguments.length; i++) { - source = arguments[i]; - for (key in source) { - target[key] = source[key]; - } +goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) { + var type = this.type = e.type; - // For IE the for-in-loop does not contain any properties that are not - // enumerable on the prototype object (for example isPrototypeOf from - // Object.prototype) and it will also not include 'replace' on objects that - // extend String and change 'replace' (not that it is common for anyone to - // extend anything except Object). + // TODO(nicksantos): Change this.target to type EventTarget. + this.target = /** @type {Node} */ (e.target) || e.srcElement; - for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) { - key = goog.object.PROTOTYPE_FIELDS_[j]; - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; + // TODO(nicksantos): Change this.currentTarget to type EventTarget. + this.currentTarget = /** @type {Node} */ (opt_currentTarget); + + var relatedTarget = /** @type {Node} */ (e.relatedTarget); + if (relatedTarget) { + // There's a bug in FireFox where sometimes, relatedTarget will be a + // chrome element, and accessing any property of it will get a permission + // denied exception. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=497780 + if (goog.userAgent.GECKO) { + if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) { + relatedTarget = null; } } - } -}; - - -/** - * Creates a new object built from the key-value pairs provided as arguments. - * @param {...*} var_args If only one argument is provided and it is an array - * then this is used as the arguments, otherwise even arguments are used as - * the property names and odd arguments are used as the property values. - * @return {!Object} The new object. - * @throws {Error} If there are uneven number of arguments or there is only one - * non array argument. - */ -goog.object.create = function(var_args) { - var argLength = arguments.length; - if (argLength == 1 && goog.isArray(arguments[0])) { - return goog.object.create.apply(null, arguments[0]); + // TODO(arv): Use goog.events.EventType when it has been refactored into its + // own file. + } else if (type == goog.events.EventType.MOUSEOVER) { + relatedTarget = e.fromElement; + } else if (type == goog.events.EventType.MOUSEOUT) { + relatedTarget = e.toElement; } - if (argLength % 2) { - throw Error('Uneven number of arguments'); - } + this.relatedTarget = relatedTarget; - var rv = {}; - for (var i = 0; i < argLength; i += 2) { - rv[arguments[i]] = arguments[i + 1]; - } - return rv; -}; + // Webkit emits a lame warning whenever layerX/layerY is accessed. + // http://code.google.com/p/chromium/issues/detail?id=101733 + this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ? + e.offsetX : e.layerX; + this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ? + e.offsetY : e.layerY; + this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; + this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; + this.screenX = e.screenX || 0; + this.screenY = e.screenY || 0; -/** - * Creates a new object where the property names come from the arguments but - * the value is always set to true - * @param {...*} var_args If only one argument is provided and it is an array - * then this is used as the arguments, otherwise the arguments are used - * as the property names. - * @return {!Object} The new object. - */ -goog.object.createSet = function(var_args) { - var argLength = arguments.length; - if (argLength == 1 && goog.isArray(arguments[0])) { - return goog.object.createSet.apply(null, arguments[0]); - } + this.button = e.button; - var rv = {}; - for (var i = 0; i < argLength; i++) { - rv[arguments[i]] = true; + this.keyCode = e.keyCode || 0; + this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0); + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.shiftKey = e.shiftKey; + this.metaKey = e.metaKey; + this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; + this.state = e.state; + this.event_ = e; + if (e.defaultPrevented) { + this.preventDefault(); } - return rv; }; /** - * Creates an immutable view of the underlying object, if the browser - * supports immutable objects. + * Tests to see which button was pressed during the event. This is really only + * useful in IE and Gecko browsers. And in IE, it's only useful for + * mousedown/mouseup events, because click only fires for the left mouse button. * - * In default mode, writes to this view will fail silently. In strict mode, - * they will throw an error. + * Safari 2 only reports the left button being clicked, and uses the value '1' + * instead of 0. Opera only reports a mousedown event for the middle button, and + * no mouse events for the right button. Opera has default behavior for left and + * middle click that can only be overridden via a configuration setting. * - * @param {!Object.} obj An object. - * @return {!Object.} An immutable view of that object, or the - * original object if this browser does not support immutables. - * @template K,V + * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html. + * + * @param {goog.events.BrowserEvent.MouseButton} button The button + * to test for. + * @return {boolean} True if button was pressed. */ -goog.object.createImmutableView = function(obj) { - var result = obj; - if (Object.isFrozen && !Object.isFrozen(obj)) { - result = Object.create(obj); - Object.freeze(result); +goog.events.BrowserEvent.prototype.isButton = function(button) { + if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) { + if (this.type == 'click') { + return button == goog.events.BrowserEvent.MouseButton.LEFT; + } else { + return !!(this.event_.button & + goog.events.BrowserEvent.IEButtonMap[button]); + } + } else { + return this.event_.button == button; } - return result; }; /** - * @param {!Object} obj An object. - * @return {boolean} Whether this is an immutable view of the object. - */ -goog.object.isImmutableView = function(obj) { - return !!Object.isFrozen && Object.isFrozen(obj); -}; -} -if(!lt.util.load.provided_QMARK_('goog.events.EventWrapper')) { -// 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 goog.events.EventWrapper interface. + * Whether this has an "action"-producing mouse button. + * + * By definition, this includes left-click on windows/linux, and left-click + * without the ctrl key on Macs. * - * @author eae@google.com (Emil A Eklund) + * @return {boolean} The result. */ - -goog.provide('goog.events.EventWrapper'); - +goog.events.BrowserEvent.prototype.isMouseActionButton = function() { + // Webkit does not ctrl+click to be a right-click, so we + // normalize it to behave like Gecko and Opera. + return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) && + !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey); +}; /** - * Interface for event wrappers. - * @interface + * @override */ -goog.events.EventWrapper = function() { +goog.events.BrowserEvent.prototype.stopPropagation = function() { + goog.events.BrowserEvent.superClass_.stopPropagation.call(this); + if (this.event_.stopPropagation) { + this.event_.stopPropagation(); + } else { + this.event_.cancelBubble = true; + } }; /** - * Adds an event listener using the wrapper on a DOM Node or an object that has - * implemented {@link goog.events.EventTarget}. A listener can only be added - * once to an object. - * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. - * @param {Function|Object} listener Callback method, or an object with a - * handleEvent function. - * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to - * false). - * @param {Object=} opt_scope Element in whose scope to call the listener. - * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add - * listener to. + * @override */ -goog.events.EventWrapper.prototype.listen = function(src, listener, opt_capt, - opt_scope, opt_eventHandler) { +goog.events.BrowserEvent.prototype.preventDefault = function() { + goog.events.BrowserEvent.superClass_.preventDefault.call(this); + var be = this.event_; + if (!be.preventDefault) { + be.returnValue = false; + if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) { + /** @preserveTry */ + try { + // Most keys can be prevented using returnValue. Some special keys + // require setting the keyCode to -1 as well: + // + // In IE7: + // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6) + // + // In IE8: + // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event) + // + // We therefore do this for all function keys as well as when Ctrl key + // is pressed. + var VK_F1 = 112; + var VK_F12 = 123; + if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) { + be.keyCode = -1; + } + } catch (ex) { + // IE throws an 'access denied' exception when trying to change + // keyCode in some situations (e.g. srcElement is input[type=file], + // or srcElement is an anchor tag rewritten by parent's innerHTML). + // Do nothing in this case. + } + } + } else { + be.preventDefault(); + } }; /** - * Removes an event listener added using goog.events.EventWrapper.listen. - * - * @param {EventTarget|goog.events.EventTarget} src The node to remove listener - * from. - * @param {Function|Object} listener Callback method, or an object with a - * handleEvent function. - * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to - * false). - * @param {Object=} opt_scope Element in whose scope to call the listener. - * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove - * listener from. + * @return {Event} The underlying browser event object. */ -goog.events.EventWrapper.prototype.unlisten = function(src, listener, opt_capt, - opt_scope, opt_eventHandler) { +goog.events.BrowserEvent.prototype.getBrowserEvent = function() { + return this.event_; }; -} -if(!lt.util.load.provided_QMARK_('goog.debug.errorHandlerWeakDep')) { -// 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; } +/** @override */ +goog.events.BrowserEvent.prototype.disposeInternal = function() { }; } if(!lt.util.load.provided_QMARK_('goog.events')) { @@ -3542,17 +3954,21 @@ if(!lt.util.load.provided_QMARK_('goog.events')) { // limitations under the License. /** - * @fileoverview Event Manager. - * - * Provides an abstracted interface to the browsers' event - * systems. This uses an indirect lookup of listener functions to avoid circular - * references between DOM (in IE) or XPCOM (in Mozilla) objects which leak - * memory. This makes it easier to write OO Javascript/DOM code. + * @fileoverview An event manager for both native browser event + * targets and custom JavaScript event targets + * ({@code goog.events.Listenable}). This provides an abstraction + * over browsers' event systems. * - * It simulates capture & bubble in Internet Explorer. + * It also provides a simulation of W3C event model's capture phase in + * Internet Explorer (IE 8 and below). Caveat: the simulation does not + * interact well with listeners registered directly on the elements + * (bypassing goog.events) or even with listeners registered via + * goog.events in a separate JS binary. In these cases, we provide + * no ordering guarantees. * - * The listeners will also automagically have their event objects patched, so - * your handlers don't need to worry about the browser. + * The listeners will receive a "patched" event object. Such event object + * contains normalized values for certain event properties that differs in + * different browsers. * * Example usage: *
@@ -3560,42 +3976,33 @@ if(!lt.util.load.provided_QMARK_('goog.events')) {
  * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
  * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
  * goog.events.removeAll(myNode);
- * goog.events.removeAll();
  * 
* * in IE and event object patching] * - * @supported IE6+, FF1.5+, WebKit, Opera. * @see ../demos/events.html * @see ../demos/event-propagation.html * @see ../demos/stopevent.html */ - -// This uses 3 lookup tables/trees. -// listenerTree_ is a tree of type -> capture -> src uid -> [Listener] -// listeners_ is a map of key -> [Listener] -// -// The key is a field of the Listener. The Listener class also has the type, -// capture and the src so one can always trace back in the tree -// -// sources_: src uid -> [Listener] - +// IMPLEMENTATION NOTES: +// goog.events stores an auxiliary data structure on each EventTarget +// source being listened on. This allows us to take advantage of GC, +// having the data structure GC'd when the EventTarget is GC'd. This +// GC behavior is equivalent to using W3C DOM Events directly. goog.provide('goog.events'); +goog.provide('goog.events.CaptureSimulationMode'); goog.provide('goog.events.Key'); +goog.provide('goog.events.ListenableType'); goog.require('goog.array'); +goog.require('goog.asserts'); goog.require('goog.debug.entryPointRegistry'); -goog.require('goog.debug.errorHandlerWeakDep'); goog.require('goog.events.BrowserEvent'); goog.require('goog.events.BrowserFeature'); -goog.require('goog.events.Event'); -goog.require('goog.events.EventWrapper'); goog.require('goog.events.Listenable'); -goog.require('goog.events.Listener'); -goog.require('goog.object'); -goog.require('goog.userAgent'); +goog.require('goog.events.ListenerMap'); /** @@ -3605,80 +4012,108 @@ goog.events.Key; /** - * @typedef {EventTarget|goog.events.Listenable|goog.events.EventTarget} + * @typedef {EventTarget|goog.events.Listenable} */ goog.events.ListenableType; /** * Container for storing event listeners and their proxies - * @private - * @type {Object.} + * + * TODO(user): Remove this when all external usage is + * purged. goog.events no longer use goog.events.listeners_ for + * anything meaningful. + * + * @private {!Object.} */ goog.events.listeners_ = {}; /** - * The root of the listener tree + * Property name on a native event target for the listener map + * associated with the event target. + * @const * @private - * @type {Object} */ -goog.events.listenerTree_ = {}; +goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0); /** - * Lookup for mapping source UIDs to listeners. + * String used to prepend to IE event types. + * @const * @private - * @type {Object} */ -goog.events.sources_ = {}; +goog.events.onString_ = 'on'; /** - * String used to prepend to IE event types. Not a constant so that it is not - * inlined. - * @type {string} + * Map of computed "on" strings for IE event types. Caching + * this removes an extra object allocation in goog.events.listen which + * improves IE6 performance. + * @const + * @dict * @private */ -goog.events.onString_ = 'on'; +goog.events.onStringMap_ = {}; /** - * Map of computed on strings for IE event types. Caching this removes an extra - * object allocation in goog.events.listen which improves IE6 performance. - * @type {Object} - * @private + * @enum {number} Different capture simulation mode for IE8-. */ -goog.events.onStringMap_ = {}; +goog.events.CaptureSimulationMode = { + /** + * Does not perform capture simulation. Will asserts in IE8- when you + * add capture listeners. + */ + OFF_AND_FAIL: 0, + + /** + * Does not perform capture simulation, silently ignore capture + * listeners. + */ + OFF_AND_SILENT: 1, + + /** + * Performs capture simulation. + */ + ON: 2 +}; /** - * Separator used to split up the various parts of an event key, to help avoid - * the possibilities of collisions. - * @type {string} - * @private + * @define {number} The capture simulation mode for IE8-. By default, + * this is ON. + */ +goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2); + + +/** + * Estimated count of total native listeners. + * @private {number} */ -goog.events.keySeparator_ = '_'; +goog.events.listenerCountEstimate_ = 0; /** - * Adds an event listener for a specific event on a DOM Node or an - * object that has implemented {@link goog.events.EventTarget}. A - * listener can only be added once to an object and if it is added - * again the key for the listener is returned. Note that if the - * existing listener is a one-off listener (registered via - * listenOnce), it will no longer be a one-off listener after a call - * to listen(). + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. A listener can only be added once + * to an object and if it is added again the key for the listener is + * returned. Note that if the existing listener is a one-off listener + * (registered via listenOnce), it will no longer be a one-off + * listener after a call to listen(). * - * @param {goog.events.ListenableType} src The node to listen to - * events on. + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. * @param {string|Array.} type Event type or array of event types. - * @param {Function|Object} listener Callback method, or an object with a - * handleEvent function. + * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. + * WARNING: passing an Object is now softly deprecated. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to * false). - * @param {Object=} opt_handler Element in whose scope to call the listener. + * @param {T=} opt_handler Element in whose scope to call the listener. * @return {goog.events.Key} Unique key for the listener. + * @template T */ goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { if (goog.isArray(type)) { @@ -3688,39 +4123,30 @@ goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { return null; } - var listenableKey; - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(src)) { - listenableKey = src.listen( - /** @type {string} */ (type), - goog.events.wrapListener_(listener), opt_capt, opt_handler); + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.listen( + /** @type {string} */ (type), listener, opt_capt, opt_handler); } else { - listenableKey = goog.events.listen_( - /** @type {EventTarget|goog.events.EventTarget} */ (src), + return goog.events.listen_( + /** @type {EventTarget} */ (src), type, listener, /* callOnce */ false, opt_capt, opt_handler); } - - var key = listenableKey.key; - goog.events.listeners_[key] = listenableKey; - return key; }; /** - * Adds an event listener for a specific event on a DOM Node or an object that - * has implemented {@link goog.events.EventTarget}. A listener can only be - * added once to an object and if it is added again the key for the listener - * is returned. + * Adds an event listener for a specific event on a native event + * target. A listener can only be added once to an object and if it + * is added again the key for the listener is returned. * * Note that a one-off listener will not change an existing listener, * if any. On the other hand a normal listener will change existing * one-off listener to become a normal listener. * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. + * @param {EventTarget} src The node to listen to events on. * @param {?string} type Event type or array of event types. - * @param {Function|Object} listener Callback method, or an object with a - * handleEvent function. + * @param {!Function} listener Callback function. * @param {boolean} callOnce Whether the listener is a one-off * listener or otherwise. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to @@ -3736,83 +4162,41 @@ goog.events.listen_ = function( } var capture = !!opt_capt; - var map = goog.events.listenerTree_; - - if (!(type in map)) { - map[type] = {count_: 0, remaining_: 0}; + if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_FAIL) { + goog.asserts.fail('Can not register capture listener in IE8-.'); + return null; + } else if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.OFF_AND_SILENT) { + return null; + } } - map = map[type]; - if (!(capture in map)) { - map[capture] = {count_: 0, remaining_: 0}; - map.count_++; + var listenerMap = goog.events.getListenerMap_(src); + if (!listenerMap) { + src[goog.events.LISTENER_MAP_PROP_] = listenerMap = + new goog.events.ListenerMap(src); } - map = map[capture]; - - var srcUid = goog.getUid(src); - var listenerArray, listenerObj; - - // The remaining_ property is used to be able to short circuit the iteration - // of the event listeners. - // - // Increment the remaining event listeners to call even if this event might - // already have been fired. At this point we do not know if the event has - // been fired and it is too expensive to find out. By incrementing it we are - // guaranteed that we will not skip any event listeners. - map.remaining_++; - - // Do not use srcUid in map here since that will cast the number to a - // string which will allocate one string object. - if (!map[srcUid]) { - listenerArray = map[srcUid] = []; - map.count_++; - } else { - listenerArray = map[srcUid]; - // Ensure that the listeners do not already contain the current listener - for (var i = 0; i < listenerArray.length; i++) { - listenerObj = listenerArray[i]; - if (listenerObj.listener == listener && - listenerObj.handler == opt_handler) { - - // If this listener has been removed we should not return its key. It - // is OK that we create new listenerObj below since the removed one - // will be cleaned up later. - if (listenerObj.removed) { - break; - } - if (!callOnce) { - // Ensure that, if there is an existing callOnce listener, it is no - // longer a callOnce listener. - listenerArray[i].callOnce = false; - } + var listenerObj = listenerMap.add( + type, listener, callOnce, opt_capt, opt_handler); - // We already have this listener. Return its key. - return listenerArray[i]; - } - } + // If the listenerObj already has a proxy, it has been set up + // previously. We simply return. + if (listenerObj.proxy) { + return listenerObj; } var proxy = goog.events.getProxy(); - listenerObj = new goog.events.Listener(); - listenerObj.init(listener, proxy, src, type, capture, opt_handler); - listenerObj.callOnce = callOnce; + listenerObj.proxy = proxy; proxy.src = src; proxy.listener = listenerObj; - listenerArray.push(listenerObj); - - if (!goog.events.sources_[srcUid]) { - goog.events.sources_[srcUid] = []; - } - goog.events.sources_[srcUid].push(listenerObj); - // Attach the proxy through the browser's API if (src.addEventListener) { - if (src == goog.global || !src.customEvent_) { - src.addEventListener(type, proxy, capture); - } + src.addEventListener(type, proxy, capture); } else { // The else above used to be else if (src.attachEvent) and then there was // another else statement that threw an exception warning the developer @@ -3822,6 +4206,7 @@ goog.events.listen_ = function( src.attachEvent(goog.events.getOnString_(type), proxy); } + goog.events.listenerCountEstimate_++; return listenerObj; }; @@ -3852,9 +4237,10 @@ goog.events.getProxy = function() { /** - * Adds an event listener for a specific event on a DomNode or an object that - * has implemented {@link goog.events.EventTarget}. After the event has fired - * the event listener is removed from the target. + * Adds an event listener for a specific event on a native event + * target (such as a DOM element) or an object that has implemented + * {@link goog.events.Listenable}. After the event has fired the event + * listener is removed from the target. * * If an existing listener already exists, listenOnce will do * nothing. In particular, if the listener was previously registered @@ -3863,13 +4249,15 @@ goog.events.getProxy = function() { * one-off listener, listenOnce does not modify the listeners (it is * still a once listener). * - * @param {goog.events.ListenableType} src The node to listen to - * events on. + * @param {EventTarget|goog.events.Listenable} src The node to listen + * to events on. * @param {string|Array.} type Event type or array of event types. - * @param {Function|Object} listener Callback method. + * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method. * @param {boolean=} opt_capt Fire in capture phase?. - * @param {Object=} opt_handler Element in whose scope to call the listener. + * @param {T=} opt_handler Element in whose scope to call the listener. * @return {goog.events.Key} Unique key for the listener. + * @template T */ goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) { if (goog.isArray(type)) { @@ -3879,37 +4267,32 @@ goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) { return null; } - var listenableKey; - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(src)) { - listenableKey = src.listenOnce( - /** @type {string} */ (type), - goog.events.wrapListener_(listener), opt_capt, opt_handler); + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { + return src.listenOnce( + /** @type {string} */ (type), listener, opt_capt, opt_handler); } else { - listenableKey = goog.events.listen_( - /** @type {EventTarget|goog.events.EventTarget} */ (src), + return goog.events.listen_( + /** @type {EventTarget} */ (src), type, listener, /* callOnce */ true, opt_capt, opt_handler); } - - var key = listenableKey.key; - goog.events.listeners_[key] = listenableKey; - return key; }; /** * Adds an event listener with a specific event wrapper on a DOM Node or an - * object that has implemented {@link goog.events.EventTarget}. A listener can + * object that has implemented {@link goog.events.Listenable}. A listener can * only be added once to an object. * - * @param {EventTarget|goog.events.EventTarget} src The node to listen to - * events on. + * @param {EventTarget|goog.events.Listenable} src The target to + * listen to events on. * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {Function|Object} listener Callback method, or an object with a - * handleEvent function. + * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener + * Callback method, or an object with a handleEvent function. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to * false). - * @param {Object=} opt_handler Element in whose scope to call the listener. + * @param {T=} opt_handler Element in whose scope to call the listener. + * @template T */ goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) { @@ -3920,11 +4303,12 @@ goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, /** * Removes an event listener which was added with listen(). * - * @param {goog.events.ListenableType} src The target to stop + * @param {EventTarget|goog.events.Listenable} src The target to stop * listening to events on. * @param {string|Array.} type The name of the event without the 'on' * prefix. - * @param {Function|Object} listener The listener function to remove. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. * @param {boolean=} opt_capt In DOM-compliant browsers, this determines * whether the listener is fired during the capture or bubble phase of the * event. @@ -3939,25 +4323,26 @@ goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) { return null; } - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(src)) { + listener = goog.events.wrapListener(listener); + if (goog.events.Listenable.isImplementedBy(src)) { return src.unlisten( - /** @type {string} */ (type), - goog.events.wrapListener_(listener), opt_capt, opt_handler); + /** @type {string} */ (type), listener, opt_capt, opt_handler); } - var capture = !!opt_capt; - - var listenerArray = goog.events.getListeners_(src, type, capture); - if (!listenerArray) { + if (!src) { + // TODO(user): We should tighten the API to only accept + // non-null objects, or add an assertion here. return false; } - for (var i = 0; i < listenerArray.length; i++) { - if (listenerArray[i].listener == listener && - listenerArray[i].capture == capture && - listenerArray[i].handler == opt_handler) { - return goog.events.unlistenByKey(listenerArray[i].key); + var capture = !!opt_capt; + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + if (listenerMap) { + var listenerObj = listenerMap.getListener( + /** @type {string} */ (type), listener, capture, opt_handler); + if (listenerObj) { + return goog.events.unlistenByKey(listenerObj); } } @@ -3974,72 +4359,49 @@ goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) { * @return {boolean} indicating whether the listener was there to remove. */ goog.events.unlistenByKey = function(key) { - // TODO(user): When we flip goog.events.Key to be ListenableKey, - // we need to change this. - var listener = goog.events.listeners_[key]; - if (!listener) { + // TODO(user): Remove this check when tests that rely on this + // are fixed. + if (goog.isNumber(key)) { return false; } - if (listener.removed) { + + var listener = /** @type {goog.events.ListenableKey} */ (key); + if (!listener || listener.removed) { return false; } var src = listener.src; - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(src)) { + if (goog.events.Listenable.isImplementedBy(src)) { return src.unlistenByKey(listener); } var type = listener.type; var proxy = listener.proxy; - var capture = listener.capture; - if (src.removeEventListener) { - // EventTarget calls unlisten so we need to ensure that the source is not - // an event target to prevent re-entry. - // TODO(arv): What is this goog.global for? Why would anyone listen to - // events on the [[Global]] object? Is it supposed to be window? Why would - // we not want to allow removing event listeners on the window? - if (src == goog.global || !src.customEvent_) { - src.removeEventListener(type, proxy, capture); - } + src.removeEventListener(type, proxy, listener.capture); } else if (src.detachEvent) { src.detachEvent(goog.events.getOnString_(type), proxy); } - - var srcUid = goog.getUid(src); - - // In a perfect implementation we would decrement the remaining_ field here - // but then we would need to know if the listener has already been fired or - // not. We therefore skip doing this and in this uncommon case the entire - // ancestor chain will need to be traversed as before. - - // Remove from sources_ - if (goog.events.sources_[srcUid]) { - var sourcesArray = goog.events.sources_[srcUid]; - goog.array.remove(sourcesArray, listener); - if (sourcesArray.length == 0) { - delete goog.events.sources_[srcUid]; + goog.events.listenerCountEstimate_--; + + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + // TODO(user): Try to remove this conditional and execute the + // first branch always. This should be safe. + if (listenerMap) { + listenerMap.removeByKey(listener); + if (listenerMap.getTypeCount() == 0) { + // Null the src, just because this is simple to do (and useful + // for IE <= 7). + listenerMap.src = null; + // We don't use delete here because IE does not allow delete + // on a window object. + src[goog.events.LISTENER_MAP_PROP_] = null; } + } else { + listener.markAsRemoved(); } - listener.removed = true; - - // There are some esoteric situations where the hash code of an object - // can change, and we won't be able to find the listenerArray anymore. - // For example, if you're listening on a window, and the user navigates to - // a different window, the UID will disappear. - // - // It should be impossible to ever find the original listenerArray, so it - // doesn't really matter if we can't clean it up in this case. - var listenerArray = goog.events.listenerTree_[type][capture][srcUid]; - if (listenerArray) { - listenerArray.needsCleanup_ = true; - goog.events.cleanUp_(type, capture, srcUid, listenerArray); - } - - delete goog.events.listeners_[key]; - return true; }; @@ -4047,10 +4409,11 @@ goog.events.unlistenByKey = function(key) { /** * Removes an event listener which was added with listenWithWrapper(). * - * @param {EventTarget|goog.events.EventTarget} src The target to stop + * @param {EventTarget|goog.events.Listenable} src The target to stop * listening to events on. * @param {goog.events.EventWrapper} wrapper Event wrapper to use. - * @param {Function|Object} listener The listener function to remove. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to remove. * @param {boolean=} opt_capt In DOM-compliant browsers, this determines * whether the listener is fired during the capture or bubble phase of the * event. @@ -4063,123 +4426,62 @@ goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, /** - * Cleans up goog.events internal data structure. This should be - * called by all implementations of goog.events.Listenable when - * removing listeners. + * Removes all listeners from an object. You can also optionally + * remove listeners of a particular type. * - * TODO(user): Once we remove numeric key support from - * goog.events.listen and friend, we will be able to remove this - * requirement. - * - * @param {goog.events.ListenableKey} listenableKey The key to clean up. - */ -goog.events.cleanUp = function(listenableKey) { - delete goog.events.listeners_[listenableKey.key]; -}; - - -/** - * Cleans up the listener array as well as the listener tree - * @param {string} type The type of the event. - * @param {boolean} capture Whether to clean up capture phase listeners instead - * bubble phase listeners. - * @param {number} srcUid The unique ID of the source. - * @param {Array.} listenerArray The array being cleaned. - * @private + * @param {Object=} opt_obj Object to remove listeners from. Not + * specifying opt_obj is now DEPRECATED (it used to remove all + * registered listeners). + * @param {string=} opt_type Type of event to, default is all types. + * @return {number} Number of listeners removed. */ -goog.events.cleanUp_ = function(type, capture, srcUid, listenerArray) { - // The listener array gets locked during the dispatch phase so that removals - // of listeners during this phase does not screw up the indeces. This method - // is called after we have removed a listener as well as after the dispatch - // phase in case any listeners were removed. - if (!listenerArray.locked_) { // catches both 0 and not set - if (listenerArray.needsCleanup_) { - // Loop over the listener array and remove listeners that have removed set - // to true. This could have been done with filter or something similar but - // we want to change the array in place and we want to minimize - // allocations. Adding a listener during this phase adds to the end of the - // array so that works fine as long as the length is rechecked every in - // iteration. - for (var oldIndex = 0, newIndex = 0; - oldIndex < listenerArray.length; - oldIndex++) { - if (listenerArray[oldIndex].removed) { - var proxy = listenerArray[oldIndex].proxy; - proxy.src = null; - continue; - } - if (oldIndex != newIndex) { - listenerArray[newIndex] = listenerArray[oldIndex]; - } - newIndex++; - } - listenerArray.length = newIndex; - - listenerArray.needsCleanup_ = false; +goog.events.removeAll = function(opt_obj, opt_type) { + // TODO(user): Change the type of opt_obj from Object= to + // !EventTarget|goog.events.Listenable). And replace this with an + // assertion. + if (!opt_obj) { + return 0; + } - // In case the length is now zero we release the object. - if (newIndex == 0) { - delete goog.events.listenerTree_[type][capture][srcUid]; - goog.events.listenerTree_[type][capture].count_--; + if (goog.events.Listenable.isImplementedBy(opt_obj)) { + return opt_obj.removeAllListeners(opt_type); + } - if (goog.events.listenerTree_[type][capture].count_ == 0) { - delete goog.events.listenerTree_[type][capture]; - goog.events.listenerTree_[type].count_--; - } + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (opt_obj)); + if (!listenerMap) { + return 0; + } - if (goog.events.listenerTree_[type].count_ == 0) { - delete goog.events.listenerTree_[type]; + var count = 0; + for (var type in listenerMap.listeners) { + if (!opt_type || type == opt_type) { + // Clone so that we don't need to worry about unlistenByKey + // changing the content of the ListenerMap. + var listeners = goog.array.clone(listenerMap.listeners[type]); + for (var i = 0; i < listeners.length; ++i) { + if (goog.events.unlistenByKey(listeners[i])) { + ++count; } } - } } + return count; }; /** - * Removes all listeners from an object, if no object is specified it will - * remove all listeners that have been registered. You can also optionally - * remove listeners of a particular type or capture phase. - * - * removeAll() will not remove listeners registered directly on a - * goog.events.Listenable and listeners registered via add(Once)Listener. - * - * @param {Object=} opt_obj Object to remove listeners from. - * @param {string=} opt_type Type of event to, default is all types. + * Removes all native listeners registered via goog.events. Native + * listeners are listeners on native browser objects (such as DOM + * elements). In particular, goog.events.Listenable and + * goog.events.EventTarget listeners will NOT be removed. * @return {number} Number of listeners removed. + * @deprecated This doesn't do anything, now that Closure no longer + * stores a central listener registry. */ -goog.events.removeAll = function(opt_obj, opt_type) { - var count = 0; - - var noObj = opt_obj == null; - var noType = opt_type == null; - - if (!noObj) { - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - opt_obj && goog.events.Listenable.isImplementedBy(opt_obj)) { - return opt_obj.removeAllListeners(opt_type); - } - - var srcUid = goog.getUid(/** @type {Object} */ (opt_obj)); - if (goog.events.sources_[srcUid]) { - var sourcesArray = goog.events.sources_[srcUid]; - for (var i = sourcesArray.length - 1; i >= 0; i--) { - var listener = sourcesArray[i]; - if (noType || opt_type == listener.type) { - goog.events.unlistenByKey(listener.key); - count++; - } - } - } - } else { - goog.object.forEach(goog.events.listeners_, function(listener, key) { - goog.events.unlistenByKey(key); - count++; - }); - } - - return count; +goog.events.removeAllNativeListeners = function() { + goog.events.listenerCountEstimate_ = 0; + return 0; }; @@ -4192,39 +4494,19 @@ goog.events.removeAll = function(opt_obj, opt_type) { * @return {Array.} Array of listener objects. */ goog.events.getListeners = function(obj, type, capture) { - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(obj)) { + if (goog.events.Listenable.isImplementedBy(obj)) { return obj.getListeners(type, capture); } else { - return goog.events.getListeners_(obj, type, capture) || []; - } -}; - - -/** - * Gets the listeners for a given object, type and capture phase. - * - * @param {Object} obj Object to get listeners for. - * @param {?string} type Event type. - * @param {boolean} capture Capture phase?. - * @return {Array.?} Array of listener objects. - * Returns null if object has no listeners of that type. - * @private - */ -goog.events.getListeners_ = function(obj, type, capture) { - var map = goog.events.listenerTree_; - if (type in map) { - map = map[type]; - if (capture in map) { - map = map[capture]; - var objUid = goog.getUid(obj); - if (map[objUid]) { - return map[objUid]; - } + if (!obj) { + // TODO(user): We should tighten the API to accept + // !EventTarget|goog.events.Listenable, and add an assertion here. + return []; } - } - return null; + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + return listenerMap ? listenerMap.getListeners(type, capture) : []; + } }; @@ -4232,10 +4514,11 @@ goog.events.getListeners_ = function(obj, type, capture) { * Gets the goog.events.Listener for the event or null if no such listener is * in use. * - * @param {EventTarget|goog.events.EventTarget} src The node from which to get - * listeners. + * @param {EventTarget|goog.events.Listenable} src The target from + * which to get listeners. * @param {?string} type The name of the event without the 'on' prefix. - * @param {Function|Object} listener The listener function to get. + * @param {function(?):?|{handleEvent:function(?):?}|null} listener The + * listener function to get. * @param {boolean=} opt_capt In DOM-compliant browsers, this determines * whether the listener is fired during the * capture or bubble phase of the event. @@ -4243,29 +4526,24 @@ goog.events.getListeners_ = function(obj, type, capture) { * @return {goog.events.ListenableKey} the found listener or null if not found. */ goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { + // TODO(user): Change type from ?string to string, or add assertion. + type = /** @type {string} */ (type); + listener = goog.events.wrapListener(listener); var capture = !!opt_capt; + if (goog.events.Listenable.isImplementedBy(src)) { + return src.getListener(type, listener, capture, opt_handler); + } - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(src)) { - return src.getListener( - /** @type {string} */ (type), - goog.events.wrapListener_(listener), capture, opt_handler); + if (!src) { + // TODO(user): We should tighten the API to only accept + // non-null objects, or add an assertion here. + return null; } - var listenerArray = goog.events.getListeners_(src, type, capture); - if (listenerArray) { - for (var i = 0; i < listenerArray.length; i++) { - // If goog.events.unlistenByKey is called during an event dispatch - // then the listener array won't get cleaned up and there might be - // 'removed' listeners in the list. Ignore those. - if (!listenerArray[i].removed && - listenerArray[i].listener == listener && - listenerArray[i].capture == capture && - listenerArray[i].handler == opt_handler) { - // We already have this listener. Return its key. - return listenerArray[i]; - } - } + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (src)); + if (listenerMap) { + return listenerMap.getListener(type, listener, capture, opt_handler); } return null; }; @@ -4276,7 +4554,8 @@ goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { * specified signature. If either the type or capture parameters are * unspecified, the function will match on the remaining criteria. * - * @param {EventTarget|goog.events.EventTarget} obj Target to get listeners for. + * @param {EventTarget|goog.events.Listenable} obj Target to get + * listeners for. * @param {string=} opt_type Event type. * @param {boolean=} opt_capture Whether to check for capture or bubble-phase * listeners. @@ -4284,37 +4563,13 @@ goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { * the requested type and/or capture phase. */ goog.events.hasListener = function(obj, opt_type, opt_capture) { - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(obj)) { + if (goog.events.Listenable.isImplementedBy(obj)) { return obj.hasListener(opt_type, opt_capture); } - var objUid = goog.getUid(obj); - var listeners = goog.events.sources_[objUid]; - - if (listeners) { - var hasType = goog.isDef(opt_type); - var hasCapture = goog.isDef(opt_capture); - - if (hasType && hasCapture) { - // Lookup in the listener tree whether the specified listener exists. - var map = goog.events.listenerTree_[opt_type]; - return !!map && !!map[opt_capture] && objUid in map[opt_capture]; - - } else if (!(hasType || hasCapture)) { - // Simple check for whether the event target has any listeners at all. - return true; - - } else { - // Iterate through the listeners for the event target to find a match. - return goog.array.some(listeners, function(listener) { - return (hasType && listener.type == opt_type) || - (hasCapture && listener.capture == opt_capture); - }); - } - } - - return false; + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture); }; @@ -4337,10 +4592,10 @@ goog.events.expose = function(e) { /** - * Returns a string wth on prepended to the specified type. This is used for IE + * Returns a string with on prepended to the specified type. This is used for IE * which expects "on" to be prepended. This function caches the string in order * to avoid extra allocations in steady state. - * @param {string} type Event type strng. + * @param {string} type Event type. * @return {string} The type string with 'on' prepended. * @private */ @@ -4362,27 +4617,16 @@ goog.events.getOnString_ = function(type) { * @return {boolean} True if all listeners returned true else false. */ goog.events.fireListeners = function(obj, type, capture, eventObject) { - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE && - goog.events.Listenable.isImplementedBy(obj)) { + if (goog.events.Listenable.isImplementedBy(obj)) { return obj.fireListeners(type, capture, eventObject); } - var map = goog.events.listenerTree_; - if (type in map) { - map = map[type]; - if (capture in map) { - return goog.events.fireListeners_(map[capture], obj, type, - capture, eventObject); - } - } - return true; + return goog.events.fireListeners_(obj, type, capture, eventObject); }; /** * Fires an object's listeners of a particular type and phase. - * - * @param {Object} map Object with listeners in it. * @param {Object} obj Object whose listeners to call. * @param {string} type Event type. * @param {boolean} capture Which event phase. @@ -4390,45 +4634,29 @@ goog.events.fireListeners = function(obj, type, capture, eventObject) { * @return {boolean} True if all listeners returned true else false. * @private */ -goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) { +goog.events.fireListeners_ = function(obj, type, capture, eventObject) { var retval = 1; - var objUid = goog.getUid(obj); - if (map[objUid]) { - var remaining = --map.remaining_; - var listenerArray = map[objUid]; - - // If locked_ is not set (and if already 0) initialize it to 1. - if (!listenerArray.locked_) { - listenerArray.locked_ = 1; - } else { - listenerArray.locked_++; - } - - try { - // Events added in the dispatch phase should not be dispatched in - // the current dispatch phase. They will be included in the next - // dispatch phase though. - var length = listenerArray.length; - for (var i = 0; i < length; i++) { + var listenerMap = goog.events.getListenerMap_( + /** @type {EventTarget} */ (obj)); + if (listenerMap) { + // TODO(user): Original code avoids array creation when there + // is no listener, so we do the same. If this optimization turns + // out to be not required, we can replace this with + // listenerMap.getListeners(type, capture) instead, which is simpler. + var listenerArray = listenerMap.listeners[type]; + if (listenerArray) { + listenerArray = goog.array.clone(listenerArray); + for (var i = 0; i < listenerArray.length; i++) { var listener = listenerArray[i]; // We might not have a listener if the listener was removed. - if (listener && !listener.removed) { + if (listener && listener.capture == capture && !listener.removed) { retval &= goog.events.fireListener(listener, eventObject) !== false; } } - } finally { - // Allow the count of targets remaining to increase (if perhaps we have - // added listeners) but do not allow it to decrease if we have reentered - // this method through a listener dispatching the same event type, - // resetting and exhausted the remaining count. - map.remaining_ = Math.max(remaining, map.remaining_); - listenerArray.locked_--; - goog.events.cleanUp_(type, capture, objUid, listenerArray); } } - return Boolean(retval); }; @@ -4441,19 +4669,26 @@ goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) { * @return {boolean} Result of listener. */ goog.events.fireListener = function(listener, eventObject) { + var listenerFn = listener.listener; + var listenerHandler = listener.handler || listener.src; + if (listener.callOnce) { - goog.events.unlistenByKey(listener.key); + goog.events.unlistenByKey(listener); } - return listener.handleEvent(eventObject); + return listenerFn.call(listenerHandler, eventObject); }; /** * Gets the total number of listeners currently in the system. * @return {number} Number of listeners. + * @deprecated This returns estimated count, now that Closure no longer + * stores a central listener registry. We still return an estimation + * to keep existing listener-related tests passing. In the near future, + * this function will be removed. */ goog.events.getTotalListenerCount = function() { - return goog.object.getCount(goog.events.listeners_); + return goog.events.listenerCountEstimate_; }; @@ -4466,8 +4701,7 @@ goog.events.getTotalListenerCount = function() { * function will return false. If one of the capture listeners calls * stopPropagation, then the bubble listeners won't fire. * - * @param {goog.events.Listenable|goog.events.EventTarget} src The - * event target. + * @param {goog.events.Listenable} src The event target. * @param {goog.events.EventLike} e Event object. * @return {boolean} If anyone called preventDefault on the event object (or * if any of the handlers returns false) this will also return false. @@ -4475,87 +4709,11 @@ goog.events.getTotalListenerCount = function() { * true. */ goog.events.dispatchEvent = function(src, e) { - if (goog.events.Listenable.USE_LISTENABLE_INTERFACE) { - return src.dispatchEvent(e); - } - - var type = e.type || e; - var map = goog.events.listenerTree_; - if (!(type in map)) { - return true; - } - - // If accepting a string or object, create a custom event object so that - // preventDefault and stopPropagation work with the event. - if (goog.isString(e)) { - e = new goog.events.Event(e, src); - } else if (!(e instanceof goog.events.Event)) { - var oldEvent = e; - e = new goog.events.Event(/** @type {string} */ (type), src); - goog.object.extend(e, oldEvent); - } else { - e.target = e.target || src; - } - - var rv = 1, ancestors; - - map = map[type]; - var hasCapture = true in map; - var targetsMap; - - if (hasCapture) { - // Build ancestors now - ancestors = []; - for (var parent = src; parent; parent = parent.getParentEventTarget()) { - ancestors.push(parent); - } - - targetsMap = map[true]; - targetsMap.remaining_ = targetsMap.count_; - - // Call capture listeners - for (var i = ancestors.length - 1; - !e.propagationStopped_ && i >= 0 && targetsMap.remaining_; - i--) { - e.currentTarget = ancestors[i]; - rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, - true, e) && - e.returnValue_ != false; - } - } - - var hasBubble = false in map; - if (hasBubble) { - targetsMap = map[false]; - targetsMap.remaining_ = targetsMap.count_; - - if (hasCapture) { // We have the ancestors. - - // Call bubble listeners - for (var i = 0; !e.propagationStopped_ && i < ancestors.length && - targetsMap.remaining_; - i++) { - e.currentTarget = ancestors[i]; - rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, - false, e) && - e.returnValue_ != false; - } - } else { - // In case we don't have capture we don't have to build up the - // ancestors array. - - for (var current = src; - !e.propagationStopped_ && current && targetsMap.remaining_; - current = current.getParentEventTarget()) { - e.currentTarget = current; - rv &= goog.events.fireListeners_(targetsMap, current, e.type, - false, e) && - e.returnValue_ != false; - } - } - } - - return Boolean(rv); + goog.asserts.assert( + goog.events.Listenable.isImplementedBy(src), + 'Can not use goog.events.dispatchEvent with ' + + 'non-goog.events.Listenable instance.'); + return src.dispatchEvent(e); }; @@ -4580,8 +4738,7 @@ goog.events.protectBrowserEventEntryPoint = function(errorHandler) { * @param {Event=} opt_evt Optional event object that gets passed in via the * native event handlers. * @return {boolean} Result of the event handler. - * @this {goog.events.EventTarget|Object} The object or Element that - * fired the event. + * @this {EventTarget} The object or Element that fired the event. * @private */ goog.events.handleBrowserEvent_ = function(listener, opt_evt) { @@ -4589,92 +4746,58 @@ goog.events.handleBrowserEvent_ = function(listener, opt_evt) { return true; } - var type = listener.type; - var map = goog.events.listenerTree_; - - if (!(type in map)) { - return true; - } - map = map[type]; - var retval, targetsMap; // Synthesize event propagation if the browser does not support W3C // event model. if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) { var ieEvent = opt_evt || /** @type {Event} */ (goog.getObjectByName('window.event')); + var evt = new goog.events.BrowserEvent(ieEvent, this); + var retval = true; - // Check if we have any capturing event listeners for this type. - var hasCapture = true in map; - var hasBubble = false in map; - - if (hasCapture) { - if (goog.events.isMarkedIeEvent_(ieEvent)) { - return true; - } - - goog.events.markIeEvent_(ieEvent); - } - - var evt = new goog.events.BrowserEvent(); - // TODO(user): update @this for this function - evt.init(ieEvent, /** @type {EventTarget} */ (this)); + if (goog.events.CAPTURE_SIMULATION_MODE == + goog.events.CaptureSimulationMode.ON) { + // If we have not marked this event yet, we should perform capture + // simulation. + if (!goog.events.isMarkedIeEvent_(ieEvent)) { + goog.events.markIeEvent_(ieEvent); - retval = true; - try { - if (hasCapture) { var ancestors = []; - - for (var parent = evt.currentTarget; - parent; + for (var parent = evt.currentTarget; parent; parent = parent.parentNode) { ancestors.push(parent); } - targetsMap = map[true]; - targetsMap.remaining_ = targetsMap.count_; - - // Call capture listeners - for (var i = ancestors.length - 1; - !evt.propagationStopped_ && i >= 0 && targetsMap.remaining_; + // Fire capture listeners. + var type = listener.type; + for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0; i--) { evt.currentTarget = ancestors[i]; - retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, - true, evt); + retval &= goog.events.fireListeners_(ancestors[i], type, true, evt); } - if (hasBubble) { - targetsMap = map[false]; - targetsMap.remaining_ = targetsMap.count_; - - // Call bubble listeners - for (var i = 0; - !evt.propagationStopped_ && i < ancestors.length && - targetsMap.remaining_; - i++) { - evt.currentTarget = ancestors[i]; - retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, - false, evt); - } + // Fire bubble listeners. + // + // We can technically rely on IE to perform bubble event + // propagation. However, it turns out that IE fires events in + // opposite order of attachEvent registration, which broke + // some code and tests that rely on the order. (While W3C DOM + // Level 2 Events TR leaves the event ordering unspecified, + // modern browsers and W3C DOM Level 3 Events Working Draft + // actually specify the order as the registration order.) + for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(ancestors[i], type, false, evt); } - - } else { - // Bubbling, let IE handle the propagation. - retval = goog.events.fireListener(listener, evt); - } - - } finally { - if (ancestors) { - ancestors.length = 0; } + } else { + retval = goog.events.fireListener(listener, evt); } return retval; - } // IE + } - // Caught a non-IE DOM event. 1 additional argument which is the event object - var be = new goog.events.BrowserEvent( - opt_evt, /** @type {EventTarget} */ (this)); - retval = goog.events.fireListener(listener, be); - return retval; + // Otherwise, simply fire the listener. + return goog.events.fireListener( + listener, new goog.events.BrowserEvent(opt_evt, this)); }; @@ -4730,8 +4853,7 @@ goog.events.isMarkedIeEvent_ = function(e) { /** * Counter to create unique event ids. - * @type {number} - * @private + * @private {number} */ goog.events.uniqueIdCounter_ = 0; @@ -4741,16 +4863,31 @@ goog.events.uniqueIdCounter_ = 0; * * @param {string} identifier The identifier. * @return {string} A unique identifier. + * @idGenerator */ goog.events.getUniqueId = function(identifier) { return identifier + '_' + goog.events.uniqueIdCounter_++; }; +/** + * @param {EventTarget} src The source object. + * @return {goog.events.ListenerMap} A listener map for the given + * source object, or null if none exists. + * @private + */ +goog.events.getListenerMap_ = function(src) { + var listenerMap = src[goog.events.LISTENER_MAP_PROP_]; + // IE serializes the property as well (e.g. when serializing outer + // HTML). So we must check that the value is of the correct type. + return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null; +}; + + /** * Expando property for listener function wrapper for Object with * handleEvent. - * @type {string} + * @const * @private */ goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' + @@ -4764,13 +4901,16 @@ goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' + * calls obj.handleEvent. If the same listener is passed to this * function more than once, the same function is guaranteed to be * returned. - * @private */ -goog.events.wrapListener_ = function(listener) { +goog.events.wrapListener = function(listener) { + goog.asserts.assert(listener, 'Listener can not be null.'); + if (goog.isFunction(listener)) { return listener; } + goog.asserts.assert( + listener.handleEvent, 'An object listener must have handleEvent method.'); return listener[goog.events.LISTENER_WRAPPER_PROP_] || (listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) { return listener.handleEvent(e); @@ -4868,7 +5008,7 @@ lt.plugins.haskell.hoogle__GT_parse = (function hoogle__GT_parse(response){retur lt.plugins.haskell.hoogle__GT_convert_doc = (function hoogle__GT_convert_doc(hoogle_doc){if((hoogle_doc == null)) {return null; } else -{var location = hoogle_doc.location;var vec__8274 = /http:\/\/hackage.haskell.org\/packages\/archive\/(.+)\/latest\/doc\/html\/(.+).html/.exec(location);var with_mod = cljs.core.nth.call(null,vec__8274,0,null);var mod_package = cljs.core.nth.call(null,vec__8274,1,null);var module_name = cljs.core.nth.call(null,vec__8274,2,null);var explanation = (((with_mod == null))?"":[cljs.core.str(" ("),cljs.core.str(mod_package),cljs.core.str(": "),cljs.core.str(clojure.string.replace.call(null,module_name,"-",".")),cljs.core.str(")")].join(''));return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"name","name",1017277949),hoogle_doc.self,new cljs.core.Keyword(null,"ns","ns",1013907767),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",1013904339),new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"href","href",1017115293),location], null),[cljs.core.str("Hoogle"),cljs.core.str(explanation)].join('')], null),new cljs.core.Keyword(null,"doc","doc",1014003882),hoogle_doc.docs], null); +{var location = hoogle_doc.location;var vec__7953 = /http:\/\/hackage.haskell.org\/packages\/archive\/(.+)\/latest\/doc\/html\/(.+).html/.exec(location);var with_mod = cljs.core.nth.call(null,vec__7953,0,null);var mod_package = cljs.core.nth.call(null,vec__7953,1,null);var module_name = cljs.core.nth.call(null,vec__7953,2,null);var explanation = (((with_mod == null))?"":[cljs.core.str(" ("),cljs.core.str(mod_package),cljs.core.str(": "),cljs.core.str(clojure.string.replace.call(null,module_name,"-",".")),cljs.core.str(")")].join(''));return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"name","name",1017277949),hoogle_doc.self,new cljs.core.Keyword(null,"ns","ns",1013907767),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",1013904339),new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"href","href",1017115293),location], null),[cljs.core.str("Hoogle"),cljs.core.str(explanation)].join('')], null),new cljs.core.Keyword(null,"doc","doc",1014003882),hoogle_doc.docs], null); } }); lt.plugins.haskell.convert_hoogle_results = (function convert_hoogle_results(results){var parsed_results = lt.plugins.haskell.hoogle__GT_parse.call(null,results);return cljs.core.map.call(null,lt.plugins.haskell.hoogle__GT_convert_doc,parsed_results); @@ -4888,8 +5028,8 @@ lt.plugins.haskell.haskell_doc_search = (function haskell_doc_search(this$,cur){ lt.object.behavior_STAR_.call(null,new cljs.core.Keyword("lt.plugins.haskell","haskell-doc-search","lt.plugins.haskell/haskell-doc-search",2214663896),new cljs.core.Keyword(null,"reaction","reaction",4441361819),lt.plugins.haskell.haskell_doc_search,new cljs.core.Keyword(null,"triggers","triggers",2516997421),new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"types+","types+",4450069060),null], null), null)); lt.plugins.haskell.symbol_token_QMARK_ = (function symbol_token_QMARK_(s){return cljs.core.re_seq.call(null,/[\w\$_\-\.\*\+\\/\?\>convert-doc", "hayoo-doc", "location", "func-name", "lt.plugins.haskell/hoogle->url", "lt.plugins.haskell/hoogle", "lt.plugins.haskell/hoogle->parse", - "lt.plugins.haskell/hoogle->convert-doc", "hoogle-doc", "vec__8274", + "lt.plugins.haskell/hoogle->convert-doc", "hoogle-doc", "vec__7953", "cljs.core/nth", "with-mod", "mod-package", "module-name", "explanation", "clojure.string/replace", "lt.plugins.haskell/convert-hoogle-results", "results", @@ -35,18 +35,18 @@ "lt.plugins.haskell/find-symbol-at-cursor", "editor", "loc", "lt.objs.editor/->cursor", "token-left", "lt.objs.editor/->token", "token-right", "cljs.core/update-in", "cljs.core/inc", - "or__6813__auto__", "cljs.core/assoc", + "or__6364__auto__", "cljs.core/assoc", "lt.plugins.haskell/with-editor", "func", "lt.plugins.haskell/inline-hoogle-doc", "doc", "cljs.core/first", "lt.objs.notifos/set-msg!", "lt.plugins.haskell/haskell-inline-doc", "token", "lt.plugins.haskell/format-inline-error", "error", - "split-error", "message-only", "cljs.core/drop", + "error-unified", "split-error", "message-only", "cljs.core/drop", "clojure.string/join", "clojure.string/trim", "message", "js/parseInt", "lt.plugins.haskell/print-inline-error", "formatted-error", "lt.plugins.haskell/print-inline-errors", "data", - "seq__8279", "cljs.core/seq", "chunk__8280", "count__8281", - "i__8282", "cljs.core/-nth", "temp__4092__auto__", - "cljs.core/chunked-seq?", "c__7561__auto__", "cljs.core/chunk-first", + "seq__7958", "cljs.core/seq", "chunk__7959", "count__7960", + "i__7961", "cljs.core/-nth", "temp__4092__auto__", + "cljs.core/chunked-seq?", "c__7112__auto__", "cljs.core/chunk-first", "cljs.core/chunk-rest", "cljs.core/count", "cljs.core/next", "lt.plugins.haskell/handle-inline-errors", "result", "cljs.core/empty?", "lt.plugins.haskell/__BEH__haskell-syntax", @@ -68,7 +68,6 @@ "lt.plugins.haskell/selection-info", "pos", "info", "cljs.core/deref", "lt.objs.editor/selection?", "lt.objs.editor/selection", "lt.objs.editor/line", - "lt.plugins.haskell/prepare-code", "code", "lt.plugins.haskell/clear-result", "line", "cljs.core/get", "lt.objs.editor/line-handle", "lt.plugins.haskell/__BEH__on-eval-one", "clojure.string/blank?", @@ -81,8 +80,8 @@ "lt.plugins.haskell/__BEH__on-exit", "lt.object/object*", "lt.plugins.haskell/find-project-dir", "file", "roots", "lt.objs.files/get-roots", "lt.objs.files/parent", "prev", - "cljs.core/=", "p1__8283#", "cljs.core/some", - "lt.objs.files/ls-sync", "p__8284", "map__8286", "cljs.core/seq?", + "cljs.core/=", "p1__7962#", "cljs.core/some", + "lt.objs.files/ls-sync", "p__7963", "map__7965", "cljs.core/seq?", "cljs.core/apply", "cljs.core/hash-map", "lt.plugins.haskell/run-haskell", "path", "name", "client", "obj", "lt.object/create", "client-id", "lt.objs.clients/->id", @@ -91,11 +90,11 @@ "lt.plugins.haskell/check-client", "lt.objs.files/exists?", "lt.plugins.haskell/handle-no-haskell", "lt.objs.clients/rem!", "lt.objs.popup/popup!", "lt.objs.platform/open", - "lt.plugins.haskell/notify", "map__8288", "haskell", "cljs.core/not", - "lt.plugins.haskell/check-all", "p__8289", "map__8291", + "lt.plugins.haskell/notify", "map__7967", "haskell", "cljs.core/not", + "lt.plugins.haskell/check-all", "p__7968", "map__7970", "lt.plugins.haskell/try-connect", "lt.objs.clients/client!", "lt.plugins.haskell/__BEH__connect", "lt.objs.sidebar.clients/add-connector", "lt.objs.dialogs/dir", - "command", "map__8293", "origin", "cljs.core/map?", - "lt.objs.clients/send", "lt.objs.eval/get-client!", "map__8295", + "command", "map__7972", "origin", "cljs.core/map?", + "lt.objs.clients/send", "lt.objs.eval/get-client!", "map__7974", "lt.objs.tabs/->path", "cm", "lt.objs.editor/->cm-ed", "string"]} \ No newline at end of file diff --git a/light-haskell.cabal b/light-haskell.cabal index 2dd7bc2..88b9c70 100644 --- a/light-haskell.cabal +++ b/light-haskell.cabal @@ -60,7 +60,8 @@ executable light-haskell aeson ==0.7.*, process >=1.1, ghc-mod >=5.1, - stylish-haskell ==0.5.* + stylish-haskell ==0.5.*, + haskell-src-exts ==1.14.* -- Directories containing source files. hs-source-dirs: haskell diff --git a/src/lt/plugins/haskell.cljs b/src/lt/plugins/haskell.cljs index b35bb63..ff96e77 100644 --- a/src/lt/plugins/haskell.cljs +++ b/src/lt/plugins/haskell.cljs @@ -296,9 +296,6 @@ :code (ed/line editor (:line pos))))] info)) -(defn prepare-code [code] - (clj-string/replace code #"^(\w+)(\s+)?=" "let $1 =")) - (defn clear-result [editor line] (when-let [result (get (@editor :widgets) [(ed/line-handle editor line) :inline])] (object/raise result :clear!))) @@ -307,7 +304,7 @@ :triggers #{:eval.one} :reaction (fn [editor] (let [info (selection-info editor) - data {:data (prepare-code (:code info)) + data {:data (:code info) :line (:line info)}] (when-not (clj-string/blank? (:code info)) (clear-result editor (:line info))