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