lib/goog/events/events.js

1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview An event manager for both native browser event
17 * targets and custom JavaScript event targets
18 * ({@code goog.events.Listenable}). This provides an abstraction
19 * over browsers' event systems.
20 *
21 * It also provides a simulation of W3C event model's capture phase in
22 * Internet Explorer (IE 8 and below). Caveat: the simulation does not
23 * interact well with listeners registered directly on the elements
24 * (bypassing goog.events) or even with listeners registered via
25 * goog.events in a separate JS binary. In these cases, we provide
26 * no ordering guarantees.
27 *
28 * The listeners will receive a "patched" event object. Such event object
29 * contains normalized values for certain event properties that differs in
30 * different browsers.
31 *
32 * Example usage:
33 * <pre>
34 * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
35 * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
36 * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
37 * goog.events.removeAll(myNode);
38 * </pre>
39 *
40 * in IE and event object patching]
41 * @author arv@google.com (Erik Arvidsson)
42 *
43 * @see ../demos/events.html
44 * @see ../demos/event-propagation.html
45 * @see ../demos/stopevent.html
46 */
47
48// IMPLEMENTATION NOTES:
49// goog.events stores an auxiliary data structure on each EventTarget
50// source being listened on. This allows us to take advantage of GC,
51// having the data structure GC'd when the EventTarget is GC'd. This
52// GC behavior is equivalent to using W3C DOM Events directly.
53
54goog.provide('goog.events');
55goog.provide('goog.events.CaptureSimulationMode');
56goog.provide('goog.events.Key');
57goog.provide('goog.events.ListenableType');
58
59goog.require('goog.asserts');
60goog.require('goog.debug.entryPointRegistry');
61goog.require('goog.events.BrowserEvent');
62goog.require('goog.events.BrowserFeature');
63goog.require('goog.events.Listenable');
64goog.require('goog.events.ListenerMap');
65
66goog.forwardDeclare('goog.debug.ErrorHandler');
67goog.forwardDeclare('goog.events.EventWrapper');
68
69
70/**
71 * @typedef {number|goog.events.ListenableKey}
72 */
73goog.events.Key;
74
75
76/**
77 * @typedef {EventTarget|goog.events.Listenable}
78 */
79goog.events.ListenableType;
80
81
82/**
83 * Property name on a native event target for the listener map
84 * associated with the event target.
85 * @private @const {string}
86 */
87goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
88
89
90/**
91 * String used to prepend to IE event types.
92 * @const
93 * @private
94 */
95goog.events.onString_ = 'on';
96
97
98/**
99 * Map of computed "on<eventname>" strings for IE event types. Caching
100 * this removes an extra object allocation in goog.events.listen which
101 * improves IE6 performance.
102 * @const
103 * @dict
104 * @private
105 */
106goog.events.onStringMap_ = {};
107
108
109/**
110 * @enum {number} Different capture simulation mode for IE8-.
111 */
112goog.events.CaptureSimulationMode = {
113 /**
114 * Does not perform capture simulation. Will asserts in IE8- when you
115 * add capture listeners.
116 */
117 OFF_AND_FAIL: 0,
118
119 /**
120 * Does not perform capture simulation, silently ignore capture
121 * listeners.
122 */
123 OFF_AND_SILENT: 1,
124
125 /**
126 * Performs capture simulation.
127 */
128 ON: 2
129};
130
131
132/**
133 * @define {number} The capture simulation mode for IE8-. By default,
134 * this is ON.
135 */
136goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
137
138
139/**
140 * Estimated count of total native listeners.
141 * @private {number}
142 */
143goog.events.listenerCountEstimate_ = 0;
144
145
146/**
147 * Adds an event listener for a specific event on a native event
148 * target (such as a DOM element) or an object that has implemented
149 * {@link goog.events.Listenable}. A listener can only be added once
150 * to an object and if it is added again the key for the listener is
151 * returned. Note that if the existing listener is a one-off listener
152 * (registered via listenOnce), it will no longer be a one-off
153 * listener after a call to listen().
154 *
155 * @param {EventTarget|goog.events.Listenable} src The node to listen
156 * to events on.
157 * @param {string|Array<string>|
158 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
159 * type Event type or array of event types.
160 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
161 * listener Callback method, or an object with a handleEvent function.
162 * WARNING: passing an Object is now softly deprecated.
163 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
164 * false).
165 * @param {T=} opt_handler Element in whose scope to call the listener.
166 * @return {goog.events.Key} Unique key for the listener.
167 * @template T,EVENTOBJ
168 */
169goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
170 if (goog.isArray(type)) {
171 for (var i = 0; i < type.length; i++) {
172 goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
173 }
174 return null;
175 }
176
177 listener = goog.events.wrapListener(listener);
178 if (goog.events.Listenable.isImplementedBy(src)) {
179 return src.listen(
180 /** @type {string|!goog.events.EventId} */ (type),
181 listener, opt_capt, opt_handler);
182 } else {
183 return goog.events.listen_(
184 /** @type {!EventTarget} */ (src),
185 /** @type {string|!goog.events.EventId} */ (type),
186 listener, /* callOnce */ false, opt_capt, opt_handler);
187 }
188};
189
190
191/**
192 * Adds an event listener for a specific event on a native event
193 * target. A listener can only be added once to an object and if it
194 * is added again the key for the listener is returned.
195 *
196 * Note that a one-off listener will not change an existing listener,
197 * if any. On the other hand a normal listener will change existing
198 * one-off listener to become a normal listener.
199 *
200 * @param {EventTarget} src The node to listen to events on.
201 * @param {string|!goog.events.EventId} type Event type.
202 * @param {!Function} listener Callback function.
203 * @param {boolean} callOnce Whether the listener is a one-off
204 * listener or otherwise.
205 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
206 * false).
207 * @param {Object=} opt_handler Element in whose scope to call the listener.
208 * @return {goog.events.ListenableKey} Unique key for the listener.
209 * @private
210 */
211goog.events.listen_ = function(
212 src, type, listener, callOnce, opt_capt, opt_handler) {
213 if (!type) {
214 throw Error('Invalid event type');
215 }
216
217 var capture = !!opt_capt;
218 if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
219 if (goog.events.CAPTURE_SIMULATION_MODE ==
220 goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
221 goog.asserts.fail('Can not register capture listener in IE8-.');
222 return null;
223 } else if (goog.events.CAPTURE_SIMULATION_MODE ==
224 goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
225 return null;
226 }
227 }
228
229 var listenerMap = goog.events.getListenerMap_(src);
230 if (!listenerMap) {
231 src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
232 new goog.events.ListenerMap(src);
233 }
234
235 var listenerObj = listenerMap.add(
236 type, listener, callOnce, opt_capt, opt_handler);
237
238 // If the listenerObj already has a proxy, it has been set up
239 // previously. We simply return.
240 if (listenerObj.proxy) {
241 return listenerObj;
242 }
243
244 var proxy = goog.events.getProxy();
245 listenerObj.proxy = proxy;
246
247 proxy.src = src;
248 proxy.listener = listenerObj;
249
250 // Attach the proxy through the browser's API
251 if (src.addEventListener) {
252 src.addEventListener(type.toString(), proxy, capture);
253 } else if (src.attachEvent) {
254 // The else if above used to be an unconditional else. It would call
255 // exception on IE11, spoiling the day of some callers. The previous
256 // incarnation of this code, from 2007, indicates that it replaced an
257 // earlier still version that caused excess allocations on IE6.
258 src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
259 } else {
260 throw Error('addEventListener and attachEvent are unavailable.');
261 }
262
263 goog.events.listenerCountEstimate_++;
264 return listenerObj;
265};
266
267
268/**
269 * Helper function for returning a proxy function.
270 * @return {!Function} A new or reused function object.
271 */
272goog.events.getProxy = function() {
273 var proxyCallbackFunction = goog.events.handleBrowserEvent_;
274 // Use a local var f to prevent one allocation.
275 var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
276 function(eventObject) {
277 return proxyCallbackFunction.call(f.src, f.listener, eventObject);
278 } :
279 function(eventObject) {
280 var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
281 // NOTE(chrishenry): In IE, we hack in a capture phase. However, if
282 // there is inline event handler which tries to prevent default (for
283 // example <a href="..." onclick="return false">...</a>) in a
284 // descendant element, the prevent default will be overridden
285 // by this listener if this listener were to return true. Hence, we
286 // return undefined.
287 if (!v) return v;
288 };
289 return f;
290};
291
292
293/**
294 * Adds an event listener for a specific event on a native event
295 * target (such as a DOM element) or an object that has implemented
296 * {@link goog.events.Listenable}. After the event has fired the event
297 * listener is removed from the target.
298 *
299 * If an existing listener already exists, listenOnce will do
300 * nothing. In particular, if the listener was previously registered
301 * via listen(), listenOnce() will not turn the listener into a
302 * one-off listener. Similarly, if there is already an existing
303 * one-off listener, listenOnce does not modify the listeners (it is
304 * still a once listener).
305 *
306 * @param {EventTarget|goog.events.Listenable} src The node to listen
307 * to events on.
308 * @param {string|Array<string>|
309 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
310 * type Event type or array of event types.
311 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
312 * listener Callback method.
313 * @param {boolean=} opt_capt Fire in capture phase?.
314 * @param {T=} opt_handler Element in whose scope to call the listener.
315 * @return {goog.events.Key} Unique key for the listener.
316 * @template T,EVENTOBJ
317 */
318goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
319 if (goog.isArray(type)) {
320 for (var i = 0; i < type.length; i++) {
321 goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
322 }
323 return null;
324 }
325
326 listener = goog.events.wrapListener(listener);
327 if (goog.events.Listenable.isImplementedBy(src)) {
328 return src.listenOnce(
329 /** @type {string|!goog.events.EventId} */ (type),
330 listener, opt_capt, opt_handler);
331 } else {
332 return goog.events.listen_(
333 /** @type {!EventTarget} */ (src),
334 /** @type {string|!goog.events.EventId} */ (type),
335 listener, /* callOnce */ true, opt_capt, opt_handler);
336 }
337};
338
339
340/**
341 * Adds an event listener with a specific event wrapper on a DOM Node or an
342 * object that has implemented {@link goog.events.Listenable}. A listener can
343 * only be added once to an object.
344 *
345 * @param {EventTarget|goog.events.Listenable} src The target to
346 * listen to events on.
347 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
348 * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
349 * Callback method, or an object with a handleEvent function.
350 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
351 * false).
352 * @param {T=} opt_handler Element in whose scope to call the listener.
353 * @template T
354 */
355goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
356 opt_handler) {
357 wrapper.listen(src, listener, opt_capt, opt_handler);
358};
359
360
361/**
362 * Removes an event listener which was added with listen().
363 *
364 * @param {EventTarget|goog.events.Listenable} src The target to stop
365 * listening to events on.
366 * @param {string|Array<string>|
367 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
368 * type Event type or array of event types to unlisten to.
369 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
370 * listener function to remove.
371 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
372 * whether the listener is fired during the capture or bubble phase of the
373 * event.
374 * @param {Object=} opt_handler Element in whose scope to call the listener.
375 * @return {?boolean} indicating whether the listener was there to remove.
376 * @template EVENTOBJ
377 */
378goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
379 if (goog.isArray(type)) {
380 for (var i = 0; i < type.length; i++) {
381 goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
382 }
383 return null;
384 }
385
386 listener = goog.events.wrapListener(listener);
387 if (goog.events.Listenable.isImplementedBy(src)) {
388 return src.unlisten(
389 /** @type {string|!goog.events.EventId} */ (type),
390 listener, opt_capt, opt_handler);
391 }
392
393 if (!src) {
394 // TODO(chrishenry): We should tighten the API to only accept
395 // non-null objects, or add an assertion here.
396 return false;
397 }
398
399 var capture = !!opt_capt;
400 var listenerMap = goog.events.getListenerMap_(
401 /** @type {!EventTarget} */ (src));
402 if (listenerMap) {
403 var listenerObj = listenerMap.getListener(
404 /** @type {string|!goog.events.EventId} */ (type),
405 listener, capture, opt_handler);
406 if (listenerObj) {
407 return goog.events.unlistenByKey(listenerObj);
408 }
409 }
410
411 return false;
412};
413
414
415/**
416 * Removes an event listener which was added with listen() by the key
417 * returned by listen().
418 *
419 * @param {goog.events.Key} key The key returned by listen() for this
420 * event listener.
421 * @return {boolean} indicating whether the listener was there to remove.
422 */
423goog.events.unlistenByKey = function(key) {
424 // TODO(chrishenry): Remove this check when tests that rely on this
425 // are fixed.
426 if (goog.isNumber(key)) {
427 return false;
428 }
429
430 var listener = key;
431 if (!listener || listener.removed) {
432 return false;
433 }
434
435 var src = listener.src;
436 if (goog.events.Listenable.isImplementedBy(src)) {
437 return src.unlistenByKey(listener);
438 }
439
440 var type = listener.type;
441 var proxy = listener.proxy;
442 if (src.removeEventListener) {
443 src.removeEventListener(type, proxy, listener.capture);
444 } else if (src.detachEvent) {
445 src.detachEvent(goog.events.getOnString_(type), proxy);
446 }
447 goog.events.listenerCountEstimate_--;
448
449 var listenerMap = goog.events.getListenerMap_(
450 /** @type {!EventTarget} */ (src));
451 // TODO(chrishenry): Try to remove this conditional and execute the
452 // first branch always. This should be safe.
453 if (listenerMap) {
454 listenerMap.removeByKey(listener);
455 if (listenerMap.getTypeCount() == 0) {
456 // Null the src, just because this is simple to do (and useful
457 // for IE <= 7).
458 listenerMap.src = null;
459 // We don't use delete here because IE does not allow delete
460 // on a window object.
461 src[goog.events.LISTENER_MAP_PROP_] = null;
462 }
463 } else {
464 listener.markAsRemoved();
465 }
466
467 return true;
468};
469
470
471/**
472 * Removes an event listener which was added with listenWithWrapper().
473 *
474 * @param {EventTarget|goog.events.Listenable} src The target to stop
475 * listening to events on.
476 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
477 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
478 * listener function to remove.
479 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
480 * whether the listener is fired during the capture or bubble phase of the
481 * event.
482 * @param {Object=} opt_handler Element in whose scope to call the listener.
483 */
484goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
485 opt_handler) {
486 wrapper.unlisten(src, listener, opt_capt, opt_handler);
487};
488
489
490/**
491 * Removes all listeners from an object. You can also optionally
492 * remove listeners of a particular type.
493 *
494 * @param {Object|undefined} obj Object to remove listeners from. Must be an
495 * EventTarget or a goog.events.Listenable.
496 * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
497 * Default is all types.
498 * @return {number} Number of listeners removed.
499 */
500goog.events.removeAll = function(obj, opt_type) {
501 // TODO(chrishenry): Change the type of obj to
502 // (!EventTarget|!goog.events.Listenable).
503
504 if (!obj) {
505 return 0;
506 }
507
508 if (goog.events.Listenable.isImplementedBy(obj)) {
509 return obj.removeAllListeners(opt_type);
510 }
511
512 var listenerMap = goog.events.getListenerMap_(
513 /** @type {!EventTarget} */ (obj));
514 if (!listenerMap) {
515 return 0;
516 }
517
518 var count = 0;
519 var typeStr = opt_type && opt_type.toString();
520 for (var type in listenerMap.listeners) {
521 if (!typeStr || type == typeStr) {
522 // Clone so that we don't need to worry about unlistenByKey
523 // changing the content of the ListenerMap.
524 var listeners = listenerMap.listeners[type].concat();
525 for (var i = 0; i < listeners.length; ++i) {
526 if (goog.events.unlistenByKey(listeners[i])) {
527 ++count;
528 }
529 }
530 }
531 }
532 return count;
533};
534
535
536/**
537 * Gets the listeners for a given object, type and capture phase.
538 *
539 * @param {Object} obj Object to get listeners for.
540 * @param {string|!goog.events.EventId} type Event type.
541 * @param {boolean} capture Capture phase?.
542 * @return {Array<goog.events.Listener>} Array of listener objects.
543 */
544goog.events.getListeners = function(obj, type, capture) {
545 if (goog.events.Listenable.isImplementedBy(obj)) {
546 return obj.getListeners(type, capture);
547 } else {
548 if (!obj) {
549 // TODO(chrishenry): We should tighten the API to accept
550 // !EventTarget|goog.events.Listenable, and add an assertion here.
551 return [];
552 }
553
554 var listenerMap = goog.events.getListenerMap_(
555 /** @type {!EventTarget} */ (obj));
556 return listenerMap ? listenerMap.getListeners(type, capture) : [];
557 }
558};
559
560
561/**
562 * Gets the goog.events.Listener for the event or null if no such listener is
563 * in use.
564 *
565 * @param {EventTarget|goog.events.Listenable} src The target from
566 * 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 The
569 * listener function to get.
570 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
571 * whether the listener is fired during the
572 * 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 EVENTOBJ
576 */
577goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
578 // TODO(chrishenry): Change type from ?string to string, or add assertion.
579 type = /** @type {string} */ (type);
580 listener = goog.events.wrapListener(listener);
581 var capture = !!opt_capt;
582 if (goog.events.Listenable.isImplementedBy(src)) {
583 return src.getListener(type, listener, capture, opt_handler);
584 }
585
586 if (!src) {
587 // TODO(chrishenry): We should tighten the API to only accept
588 // non-null objects, or add an assertion here.
589 return null;
590 }
591
592 var listenerMap = goog.events.getListenerMap_(
593 /** @type {!EventTarget} */ (src));
594 if (listenerMap) {
595 return listenerMap.getListener(type, listener, capture, opt_handler);
596 }
597 return null;
598};
599
600
601/**
602 * Returns whether an event target has any active listeners matching the
603 * specified signature. If either the type or capture parameters are
604 * unspecified, the function will match on the remaining criteria.
605 *
606 * @param {EventTarget|goog.events.Listenable} obj Target to get
607 * listeners for.
608 * @param {string|!goog.events.EventId=} opt_type Event type.
609 * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
610 * listeners.
611 * @return {boolean} Whether an event target has one or more listeners matching
612 * the requested type and/or capture phase.
613 */
614goog.events.hasListener = function(obj, opt_type, opt_capture) {
615 if (goog.events.Listenable.isImplementedBy(obj)) {
616 return obj.hasListener(opt_type, opt_capture);
617 }
618
619 var listenerMap = goog.events.getListenerMap_(
620 /** @type {!EventTarget} */ (obj));
621 return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
622};
623
624
625/**
626 * Provides a nice string showing the normalized event objects public members
627 * @param {Object} e Event Object.
628 * @return {string} String of the public members of the normalized event object.
629 */
630goog.events.expose = function(e) {
631 var str = [];
632 for (var key in e) {
633 if (e[key] && e[key].id) {
634 str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
635 } else {
636 str.push(key + ' = ' + e[key]);
637 }
638 }
639 return str.join('\n');
640};
641
642
643/**
644 * Returns a string with on prepended to the specified type. This is used for IE
645 * which expects "on" to be prepended. This function caches the string in order
646 * to avoid extra allocations in steady state.
647 * @param {string} type Event type.
648 * @return {string} The type string with 'on' prepended.
649 * @private
650 */
651goog.events.getOnString_ = function(type) {
652 if (type in goog.events.onStringMap_) {
653 return goog.events.onStringMap_[type];
654 }
655 return goog.events.onStringMap_[type] = goog.events.onString_ + type;
656};
657
658
659/**
660 * Fires an object's listeners of a particular type and phase
661 *
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) {
669 if (goog.events.Listenable.isImplementedBy(obj)) {
670 return obj.fireListeners(type, capture, eventObject);
671 }
672
673 return goog.events.fireListeners_(obj, type, capture, eventObject);
674};
675
676
677/**
678 * Fires an object's listeners of a particular type and phase.
679 * @param {Object} obj Object whose listeners to call.
680 * @param {string|!goog.events.EventId} type Event type.
681 * @param {boolean} capture Which event phase.
682 * @param {Object} eventObject Event object to be passed to listener.
683 * @return {boolean} True if all listeners returned true else false.
684 * @private
685 */
686goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
687 /** @type {boolean} */
688 var retval = true;
689
690 var listenerMap = goog.events.getListenerMap_(
691 /** @type {EventTarget} */ (obj));
692 if (listenerMap) {
693 // TODO(chrishenry): Original code avoids array creation when there
694 // is no listener, so we do the same. If this optimization turns
695 // out to be not required, we can replace this with
696 // listenerMap.getListeners(type, capture) instead, which is simpler.
697 var listenerArray = listenerMap.listeners[type.toString()];
698 if (listenerArray) {
699 listenerArray = listenerArray.concat();
700 for (var i = 0; i < listenerArray.length; i++) {
701 var listener = listenerArray[i];
702 // We might not have a listener if the listener was removed.
703 if (listener && listener.capture == capture && !listener.removed) {
704 var result = goog.events.fireListener(listener, eventObject);
705 retval = retval && (result !== false);
706 }
707 }
708 }
709 }
710 return retval;
711};
712
713
714/**
715 * Fires a listener with a set of arguments
716 *
717 * @param {goog.events.Listener} listener The listener object to call.
718 * @param {Object} eventObject The event object to pass to the listener.
719 * @return {boolean} Result of listener.
720 */
721goog.events.fireListener = function(listener, eventObject) {
722 var listenerFn = listener.listener;
723 var listenerHandler = listener.handler || listener.src;
724
725 if (listener.callOnce) {
726 goog.events.unlistenByKey(listener);
727 }
728 return listenerFn.call(listenerHandler, eventObject);
729};
730
731
732/**
733 * Gets the total number of listeners currently in the system.
734 * @return {number} Number of listeners.
735 * @deprecated This returns estimated count, now that Closure no longer
736 * stores a central listener registry. We still return an estimation
737 * to keep existing listener-related tests passing. In the near future,
738 * this function will be removed.
739 */
740goog.events.getTotalListenerCount = function() {
741 return goog.events.listenerCountEstimate_;
742};
743
744
745/**
746 * Dispatches an event (or event like object) and calls all listeners
747 * listening for events of this type. The type of the event is decided by the
748 * type property on the event object.
749 *
750 * If any of the listeners returns false OR calls preventDefault then this
751 * function will return false. If one of the capture listeners calls
752 * stopPropagation, then the bubble listeners won't fire.
753 *
754 * @param {goog.events.Listenable} src The event target.
755 * @param {goog.events.EventLike} e Event object.
756 * @return {boolean} If anyone called preventDefault on the event object (or
757 * if any of the handlers returns false) this will also return false.
758 * If there are no handlers, or if all handlers return true, this returns
759 * true.
760 */
761goog.events.dispatchEvent = function(src, e) {
762 goog.asserts.assert(
763 goog.events.Listenable.isImplementedBy(src),
764 'Can not use goog.events.dispatchEvent with ' +
765 'non-goog.events.Listenable instance.');
766 return src.dispatchEvent(e);
767};
768
769
770/**
771 * Installs exception protection for the browser event entry point using the
772 * given error handler.
773 *
774 * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
775 * protect the entry point.
776 */
777goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
778 goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
779 goog.events.handleBrowserEvent_);
780};
781
782
783/**
784 * Handles an event and dispatches it to the correct listeners. This
785 * function is a proxy for the real listener the user specified.
786 *
787 * @param {goog.events.Listener} listener The listener object.
788 * @param {Event=} opt_evt Optional event object that gets passed in via the
789 * native event handlers.
790 * @return {boolean} Result of the event handler.
791 * @this {EventTarget} The object or Element that fired the event.
792 * @private
793 */
794goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
795 if (listener.removed) {
796 return true;
797 }
798
799 // Synthesize event propagation if the browser does not support W3C
800 // event model.
801 if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
802 var ieEvent = opt_evt ||
803 /** @type {Event} */ (goog.getObjectByName('window.event'));
804 var evt = new goog.events.BrowserEvent(ieEvent, this);
805 /** @type {boolean} */
806 var retval = true;
807
808 if (goog.events.CAPTURE_SIMULATION_MODE ==
809 goog.events.CaptureSimulationMode.ON) {
810 // If we have not marked this event yet, we should perform capture
811 // simulation.
812 if (!goog.events.isMarkedIeEvent_(ieEvent)) {
813 goog.events.markIeEvent_(ieEvent);
814
815 var ancestors = [];
816 for (var parent = evt.currentTarget; parent;
817 parent = parent.parentNode) {
818 ancestors.push(parent);
819 }
820
821 // Fire capture listeners.
822 var type = listener.type;
823 for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
824 i--) {
825 evt.currentTarget = ancestors[i];
826 var result = goog.events.fireListeners_(ancestors[i], type, true, evt);
827 retval = retval && result;
828 }
829
830 // Fire bubble listeners.
831 //
832 // We can technically rely on IE to perform bubble event
833 // propagation. However, it turns out that IE fires events in
834 // opposite order of attachEvent registration, which broke
835 // some code and tests that rely on the order. (While W3C DOM
836 // Level 2 Events TR leaves the event ordering unspecified,
837 // modern browsers and W3C DOM Level 3 Events Working Draft
838 // actually specify the order as the registration order.)
839 for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
840 evt.currentTarget = ancestors[i];
841 var result = goog.events.fireListeners_(ancestors[i], type, false, evt);
842 retval = retval && result;
843 }
844 }
845 } else {
846 retval = goog.events.fireListener(listener, evt);
847 }
848 return retval;
849 }
850
851 // Otherwise, simply fire the listener.
852 return goog.events.fireListener(
853 listener, new goog.events.BrowserEvent(opt_evt, this));
854};
855
856
857/**
858 * This is used to mark the IE event object so we do not do the Closure pass
859 * twice for a bubbling event.
860 * @param {Event} e The IE browser event.
861 * @private
862 */
863goog.events.markIeEvent_ = function(e) {
864 // Only the keyCode and the returnValue can be changed. We use keyCode for
865 // non keyboard events.
866 // event.returnValue is a bit more tricky. It is undefined by default. A
867 // boolean false prevents the default action. In a window.onbeforeunload and
868 // the returnValue is non undefined it will be alerted. However, we will only
869 // modify the returnValue for keyboard events. We can get a problem if non
870 // closure events sets the keyCode or the returnValue
871
872 var useReturnValue = false;
873
874 if (e.keyCode == 0) {
875 // We cannot change the keyCode in case that srcElement is input[type=file].
876 // We could test that that is the case but that would allocate 3 objects.
877 // If we use try/catch we will only allocate extra objects in the case of a
878 // failure.
879 /** @preserveTry */
880 try {
881 e.keyCode = -1;
882 return;
883 } catch (ex) {
884 useReturnValue = true;
885 }
886 }
887
888 if (useReturnValue ||
889 /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
890 e.returnValue = true;
891 }
892};
893
894
895/**
896 * This is used to check if an IE event has already been handled by the Closure
897 * system so we do not do the Closure pass twice for a bubbling event.
898 * @param {Event} e The IE browser event.
899 * @return {boolean} True if the event object has been marked.
900 * @private
901 */
902goog.events.isMarkedIeEvent_ = function(e) {
903 return e.keyCode < 0 || e.returnValue != undefined;
904};
905
906
907/**
908 * Counter to create unique event ids.
909 * @private {number}
910 */
911goog.events.uniqueIdCounter_ = 0;
912
913
914/**
915 * Creates a unique event id.
916 *
917 * @param {string} identifier The identifier.
918 * @return {string} A unique identifier.
919 * @idGenerator
920 */
921goog.events.getUniqueId = function(identifier) {
922 return identifier + '_' + goog.events.uniqueIdCounter_++;
923};
924
925
926/**
927 * @param {EventTarget} src The source object.
928 * @return {goog.events.ListenerMap} A listener map for the given
929 * source object, or null if none exists.
930 * @private
931 */
932goog.events.getListenerMap_ = function(src) {
933 var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
934 // IE serializes the property as well (e.g. when serializing outer
935 // HTML). So we must check that the value is of the correct type.
936 return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
937};
938
939
940/**
941 * Expando property for listener function wrapper for Object with
942 * handleEvent.
943 * @private @const {string}
944 */
945goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
946 ((Math.random() * 1e9) >>> 0);
947
948
949/**
950 * @param {Object|Function} listener The listener function or an
951 * object that contains handleEvent method.
952 * @return {!Function} Either the original function or a function that
953 * calls obj.handleEvent. If the same listener is passed to this
954 * function more than once, the same function is guaranteed to be
955 * returned.
956 */
957goog.events.wrapListener = function(listener) {
958 goog.asserts.assert(listener, 'Listener can not be null.');
959
960 if (goog.isFunction(listener)) {
961 return listener;
962 }
963
964 goog.asserts.assert(
965 listener.handleEvent, 'An object listener must have handleEvent method.');
966 if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
967 listener[goog.events.LISTENER_WRAPPER_PROP_] =
968 function(e) { return listener.handleEvent(e); };
969 }
970 return listener[goog.events.LISTENER_WRAPPER_PROP_];
971};
972
973
974// Register the browser event handler as an entry point, so that
975// it can be monitored for exception handling, etc.
976goog.debug.entryPointRegistry.register(
977 /**
978 * @param {function(!Function): !Function} transformer The transforming
979 * function.
980 */
981 function(transformer) {
982 goog.events.handleBrowserEvent_ = transformer(
983 goog.events.handleBrowserEvent_);
984 });