Path: blob/trunk/third_party/closure/goog/events/events.js
2868 views
// Copyright 2005 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview An event manager for both native browser event16* targets and custom JavaScript event targets17* ({@code goog.events.Listenable}). This provides an abstraction18* over browsers' event systems.19*20* It also provides a simulation of W3C event model's capture phase in21* Internet Explorer (IE 8 and below). Caveat: the simulation does not22* interact well with listeners registered directly on the elements23* (bypassing goog.events) or even with listeners registered via24* goog.events in a separate JS binary. In these cases, we provide25* no ordering guarantees.26*27* The listeners will receive a "patched" event object. Such event object28* contains normalized values for certain event properties that differs in29* different browsers.30*31* Example usage:32* <pre>33* goog.events.listen(myNode, 'click', function(e) { alert('woo') });34* goog.events.listen(myNode, 'mouseover', mouseHandler, true);35* goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);36* goog.events.removeAll(myNode);37* </pre>38*39* in IE and event object patching]40* @author [email protected] (Erik Arvidsson)41*42* @see ../demos/events.html43* @see ../demos/event-propagation.html44* @see ../demos/stopevent.html45*/4647// IMPLEMENTATION NOTES:48// goog.events stores an auxiliary data structure on each EventTarget49// source being listened on. This allows us to take advantage of GC,50// having the data structure GC'd when the EventTarget is GC'd. This51// GC behavior is equivalent to using W3C DOM Events directly.5253goog.provide('goog.events');54goog.provide('goog.events.CaptureSimulationMode');55goog.provide('goog.events.Key');56goog.provide('goog.events.ListenableType');5758goog.require('goog.asserts');59goog.require('goog.debug.entryPointRegistry');60goog.require('goog.events.BrowserEvent');61goog.require('goog.events.BrowserFeature');62goog.require('goog.events.Listenable');63goog.require('goog.events.ListenerMap');6465goog.forwardDeclare('goog.debug.ErrorHandler');66goog.forwardDeclare('goog.events.EventWrapper');676869/**70* @typedef {number|goog.events.ListenableKey}71*/72goog.events.Key;737475/**76* @typedef {EventTarget|goog.events.Listenable}77*/78goog.events.ListenableType;798081/**82* Property name on a native event target for the listener map83* associated with the event target.84* @private @const {string}85*/86goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);878889/**90* String used to prepend to IE event types.91* @const92* @private93*/94goog.events.onString_ = 'on';959697/**98* Map of computed "on<eventname>" strings for IE event types. Caching99* this removes an extra object allocation in goog.events.listen which100* improves IE6 performance.101* @const102* @dict103* @private104*/105goog.events.onStringMap_ = {};106107108/**109* @enum {number} Different capture simulation mode for IE8-.110*/111goog.events.CaptureSimulationMode = {112/**113* Does not perform capture simulation. Will asserts in IE8- when you114* add capture listeners.115*/116OFF_AND_FAIL: 0,117118/**119* Does not perform capture simulation, silently ignore capture120* listeners.121*/122OFF_AND_SILENT: 1,123124/**125* Performs capture simulation.126*/127ON: 2128};129130131/**132* @define {number} The capture simulation mode for IE8-. By default,133* this is ON.134*/135goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);136137138/**139* Estimated count of total native listeners.140* @private {number}141*/142goog.events.listenerCountEstimate_ = 0;143144145/**146* Adds an event listener for a specific event on a native event147* target (such as a DOM element) or an object that has implemented148* {@link goog.events.Listenable}. A listener can only be added once149* to an object and if it is added again the key for the listener is150* returned. Note that if the existing listener is a one-off listener151* (registered via listenOnce), it will no longer be a one-off152* listener after a call to listen().153*154* @param {EventTarget|goog.events.Listenable} src The node to listen155* to events on.156* @param {string|Array<string>|157* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}158* type Event type or array of event types.159* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}160* listener Callback method, or an object with a handleEvent function.161* WARNING: passing an Object is now softly deprecated.162* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to163* false).164* @param {T=} opt_handler Element in whose scope to call the listener.165* @return {goog.events.Key} Unique key for the listener.166* @template T,EVENTOBJ167*/168goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {169if (goog.isArray(type)) {170for (var i = 0; i < type.length; i++) {171goog.events.listen(src, type[i], listener, opt_capt, opt_handler);172}173return null;174}175176listener = goog.events.wrapListener(listener);177if (goog.events.Listenable.isImplementedBy(src)) {178return src.listen(179/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,180opt_handler);181} else {182return goog.events.listen_(183/** @type {!EventTarget} */ (src),184/** @type {string|!goog.events.EventId} */ (type), listener,185/* callOnce */ false, opt_capt, opt_handler);186}187};188189190/**191* Adds an event listener for a specific event on a native event192* target. A listener can only be added once to an object and if it193* is added again the key for the listener is returned.194*195* Note that a one-off listener will not change an existing listener,196* if any. On the other hand a normal listener will change existing197* one-off listener to become a normal listener.198*199* @param {EventTarget} src The node to listen to events on.200* @param {string|!goog.events.EventId} type Event type.201* @param {!Function} listener Callback function.202* @param {boolean} callOnce Whether the listener is a one-off203* listener or otherwise.204* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to205* false).206* @param {Object=} opt_handler Element in whose scope to call the listener.207* @return {goog.events.ListenableKey} Unique key for the listener.208* @private209*/210goog.events.listen_ = function(211src, type, listener, callOnce, opt_capt, opt_handler) {212if (!type) {213throw Error('Invalid event type');214}215216var capture = !!opt_capt;217if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {218if (goog.events.CAPTURE_SIMULATION_MODE ==219goog.events.CaptureSimulationMode.OFF_AND_FAIL) {220goog.asserts.fail('Can not register capture listener in IE8-.');221return null;222} else if (223goog.events.CAPTURE_SIMULATION_MODE ==224goog.events.CaptureSimulationMode.OFF_AND_SILENT) {225return null;226}227}228229var listenerMap = goog.events.getListenerMap_(src);230if (!listenerMap) {231src[goog.events.LISTENER_MAP_PROP_] = listenerMap =232new goog.events.ListenerMap(src);233}234235var listenerObj = /** @type {goog.events.Listener} */ (236listenerMap.add(type, listener, callOnce, opt_capt, opt_handler));237238// If the listenerObj already has a proxy, it has been set up239// previously. We simply return.240if (listenerObj.proxy) {241return listenerObj;242}243244var proxy = goog.events.getProxy();245listenerObj.proxy = proxy;246247proxy.src = src;248proxy.listener = listenerObj;249250// Attach the proxy through the browser's API251if (src.addEventListener) {252src.addEventListener(type.toString(), proxy, capture);253} else if (src.attachEvent) {254// The else if above used to be an unconditional else. It would call255// exception on IE11, spoiling the day of some callers. The previous256// incarnation of this code, from 2007, indicates that it replaced an257// earlier still version that caused excess allocations on IE6.258src.attachEvent(goog.events.getOnString_(type.toString()), proxy);259} else {260throw Error('addEventListener and attachEvent are unavailable.');261}262263goog.events.listenerCountEstimate_++;264return listenerObj;265};266267268/**269* Helper function for returning a proxy function.270* @return {!Function} A new or reused function object.271*/272goog.events.getProxy = function() {273var proxyCallbackFunction = goog.events.handleBrowserEvent_;274// Use a local var f to prevent one allocation.275var f =276goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? function(eventObject) {277return proxyCallbackFunction.call(f.src, f.listener, eventObject);278} : function(eventObject) {279var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);280// NOTE(chrishenry): In IE, we hack in a capture phase. However, if281// there is inline event handler which tries to prevent default (for282// example <a href="..." onclick="return false">...</a>) in a283// descendant element, the prevent default will be overridden284// by this listener if this listener were to return true. Hence, we285// return undefined.286if (!v) return v;287};288return f;289};290291292/**293* Adds an event listener for a specific event on a native event294* target (such as a DOM element) or an object that has implemented295* {@link goog.events.Listenable}. After the event has fired the event296* listener is removed from the target.297*298* If an existing listener already exists, listenOnce will do299* nothing. In particular, if the listener was previously registered300* via listen(), listenOnce() will not turn the listener into a301* one-off listener. Similarly, if there is already an existing302* one-off listener, listenOnce does not modify the listeners (it is303* still a once listener).304*305* @param {EventTarget|goog.events.Listenable} src The node to listen306* to events on.307* @param {string|Array<string>|308* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}309* type Event type or array of event types.310* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}311* listener Callback method.312* @param {boolean=} opt_capt Fire in capture phase?.313* @param {T=} opt_handler Element in whose scope to call the listener.314* @return {goog.events.Key} Unique key for the listener.315* @template T,EVENTOBJ316*/317goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {318if (goog.isArray(type)) {319for (var i = 0; i < type.length; i++) {320goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);321}322return null;323}324325listener = goog.events.wrapListener(listener);326if (goog.events.Listenable.isImplementedBy(src)) {327return src.listenOnce(328/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,329opt_handler);330} else {331return goog.events.listen_(332/** @type {!EventTarget} */ (src),333/** @type {string|!goog.events.EventId} */ (type), listener,334/* callOnce */ true, opt_capt, opt_handler);335}336};337338339/**340* Adds an event listener with a specific event wrapper on a DOM Node or an341* object that has implemented {@link goog.events.Listenable}. A listener can342* only be added once to an object.343*344* @param {EventTarget|goog.events.Listenable} src The target to345* listen to events on.346* @param {goog.events.EventWrapper} wrapper Event wrapper to use.347* @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener348* Callback method, or an object with a handleEvent function.349* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to350* false).351* @param {T=} opt_handler Element in whose scope to call the listener.352* @template T353*/354goog.events.listenWithWrapper = function(355src, wrapper, listener, opt_capt, opt_handler) {356wrapper.listen(src, listener, opt_capt, opt_handler);357};358359360/**361* Removes an event listener which was added with listen().362*363* @param {EventTarget|goog.events.Listenable} src The target to stop364* listening to events on.365* @param {string|Array<string>|366* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}367* type Event type or array of event types to unlisten to.368* @param {function(?):?|{handleEvent:function(?):?}|null} listener The369* listener function to remove.370* @param {boolean=} opt_capt In DOM-compliant browsers, this determines371* whether the listener is fired during the capture or bubble phase of the372* event.373* @param {Object=} opt_handler Element in whose scope to call the listener.374* @return {?boolean} indicating whether the listener was there to remove.375* @template EVENTOBJ376*/377goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {378if (goog.isArray(type)) {379for (var i = 0; i < type.length; i++) {380goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);381}382return null;383}384385listener = goog.events.wrapListener(listener);386if (goog.events.Listenable.isImplementedBy(src)) {387return src.unlisten(388/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,389opt_handler);390}391392if (!src) {393// TODO(chrishenry): We should tighten the API to only accept394// non-null objects, or add an assertion here.395return false;396}397398var capture = !!opt_capt;399var listenerMap = goog.events.getListenerMap_(400/** @type {!EventTarget} */ (src));401if (listenerMap) {402var listenerObj = listenerMap.getListener(403/** @type {string|!goog.events.EventId} */ (type), listener, capture,404opt_handler);405if (listenerObj) {406return goog.events.unlistenByKey(listenerObj);407}408}409410return false;411};412413414/**415* Removes an event listener which was added with listen() by the key416* returned by listen().417*418* @param {goog.events.Key} key The key returned by listen() for this419* event listener.420* @return {boolean} indicating whether the listener was there to remove.421*/422goog.events.unlistenByKey = function(key) {423// TODO(chrishenry): Remove this check when tests that rely on this424// are fixed.425if (goog.isNumber(key)) {426return false;427}428429var listener = key;430if (!listener || listener.removed) {431return false;432}433434var src = listener.src;435if (goog.events.Listenable.isImplementedBy(src)) {436return /** @type {!goog.events.Listenable} */ (src).unlistenByKey(listener);437}438439var type = listener.type;440var proxy = listener.proxy;441if (src.removeEventListener) {442src.removeEventListener(type, proxy, listener.capture);443} else if (src.detachEvent) {444src.detachEvent(goog.events.getOnString_(type), proxy);445}446goog.events.listenerCountEstimate_--;447448var listenerMap = goog.events.getListenerMap_(449/** @type {!EventTarget} */ (src));450// TODO(chrishenry): Try to remove this conditional and execute the451// first branch always. This should be safe.452if (listenerMap) {453listenerMap.removeByKey(listener);454if (listenerMap.getTypeCount() == 0) {455// Null the src, just because this is simple to do (and useful456// for IE <= 7).457listenerMap.src = null;458// We don't use delete here because IE does not allow delete459// on a window object.460src[goog.events.LISTENER_MAP_PROP_] = null;461}462} else {463/** @type {!goog.events.Listener} */ (listener).markAsRemoved();464}465466return true;467};468469470/**471* Removes an event listener which was added with listenWithWrapper().472*473* @param {EventTarget|goog.events.Listenable} src The target to stop474* listening to events on.475* @param {goog.events.EventWrapper} wrapper Event wrapper to use.476* @param {function(?):?|{handleEvent:function(?):?}|null} listener The477* listener function to remove.478* @param {boolean=} opt_capt In DOM-compliant browsers, this determines479* whether the listener is fired during the capture or bubble phase of the480* event.481* @param {Object=} opt_handler Element in whose scope to call the listener.482*/483goog.events.unlistenWithWrapper = function(484src, wrapper, listener, opt_capt, opt_handler) {485wrapper.unlisten(src, listener, opt_capt, opt_handler);486};487488489/**490* Removes all listeners from an object. You can also optionally491* remove listeners of a particular type.492*493* @param {Object|undefined} obj Object to remove listeners from. Must be an494* EventTarget or a goog.events.Listenable.495* @param {string|!goog.events.EventId=} opt_type Type of event to remove.496* Default is all types.497* @return {number} Number of listeners removed.498*/499goog.events.removeAll = function(obj, opt_type) {500// TODO(chrishenry): Change the type of obj to501// (!EventTarget|!goog.events.Listenable).502503if (!obj) {504return 0;505}506507if (goog.events.Listenable.isImplementedBy(obj)) {508return /** @type {?} */ (obj).removeAllListeners(opt_type);509}510511var listenerMap = goog.events.getListenerMap_(512/** @type {!EventTarget} */ (obj));513if (!listenerMap) {514return 0;515}516517var count = 0;518var typeStr = opt_type && opt_type.toString();519for (var type in listenerMap.listeners) {520if (!typeStr || type == typeStr) {521// Clone so that we don't need to worry about unlistenByKey522// changing the content of the ListenerMap.523var listeners = listenerMap.listeners[type].concat();524for (var i = 0; i < listeners.length; ++i) {525if (goog.events.unlistenByKey(listeners[i])) {526++count;527}528}529}530}531return count;532};533534535/**536* Gets the listeners for a given object, type and capture phase.537*538* @param {Object} obj Object to get listeners for.539* @param {string|!goog.events.EventId} type Event type.540* @param {boolean} capture Capture phase?.541* @return {Array<!goog.events.Listener>} Array of listener objects.542*/543goog.events.getListeners = function(obj, type, capture) {544if (goog.events.Listenable.isImplementedBy(obj)) {545return /** @type {!goog.events.Listenable} */ (obj).getListeners(546type, capture);547} else {548if (!obj) {549// TODO(chrishenry): We should tighten the API to accept550// !EventTarget|goog.events.Listenable, and add an assertion here.551return [];552}553554var listenerMap = goog.events.getListenerMap_(555/** @type {!EventTarget} */ (obj));556return listenerMap ? listenerMap.getListeners(type, capture) : [];557}558};559560561/**562* Gets the goog.events.Listener for the event or null if no such listener is563* in use.564*565* @param {EventTarget|goog.events.Listenable} src The target from566* which to get listeners.567* @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.568* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The569* listener function to get.570* @param {boolean=} opt_capt In DOM-compliant browsers, this determines571* whether the listener is fired during the572* capture or bubble phase of the event.573* @param {Object=} opt_handler Element in whose scope to call the listener.574* @return {goog.events.ListenableKey} the found listener or null if not found.575* @template EVENTOBJ576*/577goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {578// TODO(chrishenry): Change type from ?string to string, or add assertion.579type = /** @type {string} */ (type);580listener = goog.events.wrapListener(listener);581var capture = !!opt_capt;582if (goog.events.Listenable.isImplementedBy(src)) {583return src.getListener(type, listener, capture, opt_handler);584}585586if (!src) {587// TODO(chrishenry): We should tighten the API to only accept588// non-null objects, or add an assertion here.589return null;590}591592var listenerMap = goog.events.getListenerMap_(593/** @type {!EventTarget} */ (src));594if (listenerMap) {595return listenerMap.getListener(type, listener, capture, opt_handler);596}597return null;598};599600601/**602* Returns whether an event target has any active listeners matching the603* specified signature. If either the type or capture parameters are604* unspecified, the function will match on the remaining criteria.605*606* @param {EventTarget|goog.events.Listenable} obj Target to get607* listeners for.608* @param {string|!goog.events.EventId=} opt_type Event type.609* @param {boolean=} opt_capture Whether to check for capture or bubble-phase610* listeners.611* @return {boolean} Whether an event target has one or more listeners matching612* the requested type and/or capture phase.613*/614goog.events.hasListener = function(obj, opt_type, opt_capture) {615if (goog.events.Listenable.isImplementedBy(obj)) {616return obj.hasListener(opt_type, opt_capture);617}618619var listenerMap = goog.events.getListenerMap_(620/** @type {!EventTarget} */ (obj));621return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);622};623624625/**626* Provides a nice string showing the normalized event objects public members627* @param {Object} e Event Object.628* @return {string} String of the public members of the normalized event object.629*/630goog.events.expose = function(e) {631var str = [];632for (var key in e) {633if (e[key] && e[key].id) {634str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');635} else {636str.push(key + ' = ' + e[key]);637}638}639return str.join('\n');640};641642643/**644* Returns a string with on prepended to the specified type. This is used for IE645* which expects "on" to be prepended. This function caches the string in order646* to avoid extra allocations in steady state.647* @param {string} type Event type.648* @return {string} The type string with 'on' prepended.649* @private650*/651goog.events.getOnString_ = function(type) {652if (type in goog.events.onStringMap_) {653return goog.events.onStringMap_[type];654}655return goog.events.onStringMap_[type] = goog.events.onString_ + type;656};657658659/**660* Fires an object's listeners of a particular type and phase661*662* @param {Object} obj Object whose listeners to call.663* @param {string|!goog.events.EventId} type Event type.664* @param {boolean} capture Which event phase.665* @param {Object} eventObject Event object to be passed to listener.666* @return {boolean} True if all listeners returned true else false.667*/668goog.events.fireListeners = function(obj, type, capture, eventObject) {669if (goog.events.Listenable.isImplementedBy(obj)) {670return /** @type {!goog.events.Listenable} */ (obj).fireListeners(671type, capture, eventObject);672}673674return goog.events.fireListeners_(obj, type, capture, eventObject);675};676677678/**679* Fires an object's listeners of a particular type and phase.680* @param {Object} obj Object whose listeners to call.681* @param {string|!goog.events.EventId} type Event type.682* @param {boolean} capture Which event phase.683* @param {Object} eventObject Event object to be passed to listener.684* @return {boolean} True if all listeners returned true else false.685* @private686*/687goog.events.fireListeners_ = function(obj, type, capture, eventObject) {688/** @type {boolean} */689var retval = true;690691var listenerMap = goog.events.getListenerMap_(692/** @type {EventTarget} */ (obj));693if (listenerMap) {694// TODO(chrishenry): Original code avoids array creation when there695// is no listener, so we do the same. If this optimization turns696// out to be not required, we can replace this with697// listenerMap.getListeners(type, capture) instead, which is simpler.698var listenerArray = listenerMap.listeners[type.toString()];699if (listenerArray) {700listenerArray = listenerArray.concat();701for (var i = 0; i < listenerArray.length; i++) {702var listener = listenerArray[i];703// We might not have a listener if the listener was removed.704if (listener && listener.capture == capture && !listener.removed) {705var result = goog.events.fireListener(listener, eventObject);706retval = retval && (result !== false);707}708}709}710}711return retval;712};713714715/**716* Fires a listener with a set of arguments717*718* @param {goog.events.Listener} listener The listener object to call.719* @param {Object} eventObject The event object to pass to the listener.720* @return {*} Result of listener.721*/722goog.events.fireListener = function(listener, eventObject) {723var listenerFn = listener.listener;724var listenerHandler = listener.handler || listener.src;725726if (listener.callOnce) {727goog.events.unlistenByKey(listener);728}729return listenerFn.call(listenerHandler, eventObject);730};731732733/**734* Gets the total number of listeners currently in the system.735* @return {number} Number of listeners.736* @deprecated This returns estimated count, now that Closure no longer737* stores a central listener registry. We still return an estimation738* to keep existing listener-related tests passing. In the near future,739* this function will be removed.740*/741goog.events.getTotalListenerCount = function() {742return goog.events.listenerCountEstimate_;743};744745746/**747* Dispatches an event (or event like object) and calls all listeners748* listening for events of this type. The type of the event is decided by the749* type property on the event object.750*751* If any of the listeners returns false OR calls preventDefault then this752* function will return false. If one of the capture listeners calls753* stopPropagation, then the bubble listeners won't fire.754*755* @param {goog.events.Listenable} src The event target.756* @param {goog.events.EventLike} e Event object.757* @return {boolean} If anyone called preventDefault on the event object (or758* if any of the handlers returns false) this will also return false.759* If there are no handlers, or if all handlers return true, this returns760* true.761*/762goog.events.dispatchEvent = function(src, e) {763goog.asserts.assert(764goog.events.Listenable.isImplementedBy(src),765'Can not use goog.events.dispatchEvent with ' +766'non-goog.events.Listenable instance.');767return src.dispatchEvent(e);768};769770771/**772* Installs exception protection for the browser event entry point using the773* given error handler.774*775* @param {goog.debug.ErrorHandler} errorHandler Error handler with which to776* protect the entry point.777*/778goog.events.protectBrowserEventEntryPoint = function(errorHandler) {779goog.events.handleBrowserEvent_ =780errorHandler.protectEntryPoint(goog.events.handleBrowserEvent_);781};782783784/**785* Handles an event and dispatches it to the correct listeners. This786* function is a proxy for the real listener the user specified.787*788* @param {goog.events.Listener} listener The listener object.789* @param {Event=} opt_evt Optional event object that gets passed in via the790* native event handlers.791* @return {*} Result of the event handler.792* @this {EventTarget} The object or Element that fired the event.793* @private794*/795goog.events.handleBrowserEvent_ = function(listener, opt_evt) {796if (listener.removed) {797return true;798}799800// Synthesize event propagation if the browser does not support W3C801// event model.802if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {803var ieEvent = opt_evt ||804/** @type {Event} */ (goog.getObjectByName('window.event'));805var evt = new goog.events.BrowserEvent(ieEvent, this);806/** @type {*} */807var retval = true;808809if (goog.events.CAPTURE_SIMULATION_MODE ==810goog.events.CaptureSimulationMode.ON) {811// If we have not marked this event yet, we should perform capture812// simulation.813if (!goog.events.isMarkedIeEvent_(ieEvent)) {814goog.events.markIeEvent_(ieEvent);815816var ancestors = [];817for (var parent = evt.currentTarget; parent;818parent = parent.parentNode) {819ancestors.push(parent);820}821822// Fire capture listeners.823var type = listener.type;824for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;825i--) {826evt.currentTarget = ancestors[i];827var result =828goog.events.fireListeners_(ancestors[i], type, true, evt);829retval = retval && result;830}831832// Fire bubble listeners.833//834// We can technically rely on IE to perform bubble event835// propagation. However, it turns out that IE fires events in836// opposite order of attachEvent registration, which broke837// some code and tests that rely on the order. (While W3C DOM838// Level 2 Events TR leaves the event ordering unspecified,839// modern browsers and W3C DOM Level 3 Events Working Draft840// actually specify the order as the registration order.)841for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {842evt.currentTarget = ancestors[i];843var result =844goog.events.fireListeners_(ancestors[i], type, false, evt);845retval = retval && result;846}847}848} else {849retval = goog.events.fireListener(listener, evt);850}851return retval;852}853854// Otherwise, simply fire the listener.855return goog.events.fireListener(856listener, new goog.events.BrowserEvent(opt_evt, this));857};858859860/**861* This is used to mark the IE event object so we do not do the Closure pass862* twice for a bubbling event.863* @param {Event} e The IE browser event.864* @private865*/866goog.events.markIeEvent_ = function(e) {867// Only the keyCode and the returnValue can be changed. We use keyCode for868// non keyboard events.869// event.returnValue is a bit more tricky. It is undefined by default. A870// boolean false prevents the default action. In a window.onbeforeunload and871// the returnValue is non undefined it will be alerted. However, we will only872// modify the returnValue for keyboard events. We can get a problem if non873// closure events sets the keyCode or the returnValue874875var useReturnValue = false;876877if (e.keyCode == 0) {878// We cannot change the keyCode in case that srcElement is input[type=file].879// We could test that that is the case but that would allocate 3 objects.880// If we use try/catch we will only allocate extra objects in the case of a881// failure.882883try {884e.keyCode = -1;885return;886} catch (ex) {887useReturnValue = true;888}889}890891if (useReturnValue ||892/** @type {boolean|undefined} */ (e.returnValue) == undefined) {893e.returnValue = true;894}895};896897898/**899* This is used to check if an IE event has already been handled by the Closure900* system so we do not do the Closure pass twice for a bubbling event.901* @param {Event} e The IE browser event.902* @return {boolean} True if the event object has been marked.903* @private904*/905goog.events.isMarkedIeEvent_ = function(e) {906return e.keyCode < 0 || e.returnValue != undefined;907};908909910/**911* Counter to create unique event ids.912* @private {number}913*/914goog.events.uniqueIdCounter_ = 0;915916917/**918* Creates a unique event id.919*920* @param {string} identifier The identifier.921* @return {string} A unique identifier.922* @idGenerator {unique}923*/924goog.events.getUniqueId = function(identifier) {925return identifier + '_' + goog.events.uniqueIdCounter_++;926};927928929/**930* @param {EventTarget} src The source object.931* @return {goog.events.ListenerMap} A listener map for the given932* source object, or null if none exists.933* @private934*/935goog.events.getListenerMap_ = function(src) {936var listenerMap = src[goog.events.LISTENER_MAP_PROP_];937// IE serializes the property as well (e.g. when serializing outer938// HTML). So we must check that the value is of the correct type.939return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;940};941942943/**944* Expando property for listener function wrapper for Object with945* handleEvent.946* @private @const {string}947*/948goog.events.LISTENER_WRAPPER_PROP_ =949'__closure_events_fn_' + ((Math.random() * 1e9) >>> 0);950951952/**953* @param {Object|Function} listener The listener function or an954* object that contains handleEvent method.955* @return {!Function} Either the original function or a function that956* calls obj.handleEvent. If the same listener is passed to this957* function more than once, the same function is guaranteed to be958* returned.959*/960goog.events.wrapListener = function(listener) {961goog.asserts.assert(listener, 'Listener can not be null.');962963if (goog.isFunction(listener)) {964return listener;965}966967goog.asserts.assert(968listener.handleEvent, 'An object listener must have handleEvent method.');969if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {970listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) {971return /** @type {?} */ (listener).handleEvent(e);972};973}974return listener[goog.events.LISTENER_WRAPPER_PROP_];975};976977978// Register the browser event handler as an entry point, so that979// it can be monitored for exception handling, etc.980goog.debug.entryPointRegistry.register(981/**982* @param {function(!Function): !Function} transformer The transforming983* function.984*/985function(transformer) {986goog.events.handleBrowserEvent_ =987transformer(goog.events.handleBrowserEvent_);988});989990991