events.js 33 KB

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