blob: ba7980eb084f3177456aca1c46b573cd11f410d7 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2016 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5var listen;
6var unlisten;
7var unlistenByKey;
8
9(function() {
10 let listeners = {};
11 let nextId = 0;
12
13 function getHashCode_(obj) {
14 if (obj.listen_hc_ == null) {
15 obj.listen_hc_ = ++nextId;
16 }
17 return obj.listen_hc_;
18 }
19
20 /**
21 * Takes a node, event, listener, and capture flag to create a key
22 * to identify the tuple in the listeners hash.
23 *
24 * @param {Element} node The node to listen to events on.
25 * @param {string} event The name of the event without the "on" prefix.
26 * @param {Function} listener A function to call when the event occurs.
27 * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
28 * whether the listener is fired during the
29 * capture or bubble phase of the event.
30 * @return {string} key to identify this tuple in the listeners hash.
31 */
32 function createKey_(node, event, listener, opt_useCapture) {
33 let nodeHc = getHashCode_(node);
34 let listenerHc = getHashCode_(listener);
35 opt_useCapture = !!opt_useCapture;
36 let key = nodeHc + '_' + event + '_' + listenerHc + '_' + opt_useCapture;
37 return key;
38 }
39
40 /**
41 * Adds an event listener to a DOM node for a specific event.
42 *
43 * Listen() and unlisten() use an indirect lookup of listener functions
44 * to avoid circular references between DOM (in IE) or XPCOM (in Mozilla)
45 * objects which leak memory. This makes it easier to write OO
46 * Javascript/DOM code.
47 *
48 * Examples:
49 * listen(myButton, 'click', myHandler, true);
50 * listen(myButton, 'click', this.myHandler.bind(this), true);
51 *
52 * @param {Element} node The node to listen to events on.
53 * @param {string} event The name of the event without the "on" prefix.
54 * @param {Function} listener A function to call when the event occurs.
55 * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
56 * whether the listener is fired during the
57 * capture or bubble phase of the event.
58 * @return {string} a unique key to indentify this listener.
59 */
60 listen = function(node, event, listener, opt_useCapture) {
61 let key = createKey_(node, event, listener, opt_useCapture);
62
63 // addEventListener does not allow multiple listeners
64 if (key in listeners) {
65 return key;
66 }
67
68 let proxy = handleEvent.bind(null, key);
69 listeners[key] = {
70 listener: listener,
71 proxy: proxy,
72 event: event,
73 node: node,
74 useCapture: opt_useCapture,
75 };
76
77 if (node.addEventListener) {
78 node.addEventListener(event, proxy, opt_useCapture);
79 } else if (node.attachEvent) {
80 node.attachEvent('on' + event, proxy);
81 } else {
82 throw new Error('Node {' + node + '} does not support event listeners.');
83 }
84
85 return key;
86 };
87
88 /**
89 * Removes an event listener which was added with listen().
90 *
91 * @param {Element} node The node to stop listening to events on.
92 * @param {string} event The name of the event without the "on" prefix.
93 * @param {Function} listener The listener function to remove.
94 * @param {boolean} opt_useCapture In DOM-compliant browsers, this determines
95 * whether the listener is fired during the
96 * capture or bubble phase of the event.
97 * @return {boolean} indicating whether the listener was there to remove.
98 */
99 unlisten = function(node, event, listener, opt_useCapture) {
100 let key = createKey_(node, event, listener, opt_useCapture);
101
102 return unlistenByKey(key);
103 };
104
105 /**
106 * Variant of {@link unlisten} that takes a key that was returned by
107 * {@link listen} and removes that listener.
108 *
109 * @param {string} key Key of event to be unlistened.
110 * @return {boolean} indicating whether it was there to be removed.
111 */
112 unlistenByKey = function(key) {
113 if (!(key in listeners)) {
114 return false;
115 }
116 let listener = listeners[key];
117 let proxy = listener.proxy;
118 let event = listener.event;
119 let node = listener.node;
120 let useCapture = listener.useCapture;
121
122 if (node.removeEventListener) {
123 node.removeEventListener(event, proxy, useCapture);
124 } else if (node.detachEvent) {
125 node.detachEvent('on' + event, proxy);
126 }
127
128 delete listeners[key];
129 return true;
130 };
131
132 /**
133 * The function which is actually called when the DOM event occurs. This
134 * function is a proxy for the real listener the user specified.
135 */
136 function handleEvent(key) {
137 // pass all arguments which were sent to this function except listenerID
138 // on to the actual listener.
139 let args = Array.prototype.splice.call(arguments, 1, arguments.length);
140 return listeners[key].listener.apply(null, args);
141 }
142})();