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