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