UNPKG

8.98 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _prodInvariant = require('./reactProdInvariant');
12
13var EventPluginRegistry = require('./EventPluginRegistry');
14var EventPluginUtils = require('./EventPluginUtils');
15var ReactErrorUtils = require('./ReactErrorUtils');
16
17var accumulateInto = require('./accumulateInto');
18var forEachAccumulated = require('./forEachAccumulated');
19var invariant = require('fbjs/lib/invariant');
20
21/**
22 * Internal store for event listeners
23 */
24var listenerBank = {};
25
26/**
27 * Internal queue of events that have accumulated their dispatches and are
28 * waiting to have their dispatches executed.
29 */
30var eventQueue = null;
31
32/**
33 * Dispatches an event and releases it back into the pool, unless persistent.
34 *
35 * @param {?object} event Synthetic event to be dispatched.
36 * @param {boolean} simulated If the event is simulated (changes exn behavior)
37 * @private
38 */
39var executeDispatchesAndRelease = function (event, simulated) {
40 if (event) {
41 EventPluginUtils.executeDispatchesInOrder(event, simulated);
42
43 if (!event.isPersistent()) {
44 event.constructor.release(event);
45 }
46 }
47};
48var executeDispatchesAndReleaseSimulated = function (e) {
49 return executeDispatchesAndRelease(e, true);
50};
51var executeDispatchesAndReleaseTopLevel = function (e) {
52 return executeDispatchesAndRelease(e, false);
53};
54
55var getDictionaryKey = function (inst) {
56 // Prevents V8 performance issue:
57 // https://github.com/facebook/react/pull/7232
58 return '.' + inst._rootNodeID;
59};
60
61function isInteractive(tag) {
62 return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea';
63}
64
65function shouldPreventMouseEvent(name, type, props) {
66 switch (name) {
67 case 'onClick':
68 case 'onClickCapture':
69 case 'onDoubleClick':
70 case 'onDoubleClickCapture':
71 case 'onMouseDown':
72 case 'onMouseDownCapture':
73 case 'onMouseMove':
74 case 'onMouseMoveCapture':
75 case 'onMouseUp':
76 case 'onMouseUpCapture':
77 return !!(props.disabled && isInteractive(type));
78 default:
79 return false;
80 }
81}
82
83/**
84 * This is a unified interface for event plugins to be installed and configured.
85 *
86 * Event plugins can implement the following properties:
87 *
88 * `extractEvents` {function(string, DOMEventTarget, string, object): *}
89 * Required. When a top-level event is fired, this method is expected to
90 * extract synthetic events that will in turn be queued and dispatched.
91 *
92 * `eventTypes` {object}
93 * Optional, plugins that fire events must publish a mapping of registration
94 * names that are used to register listeners. Values of this mapping must
95 * be objects that contain `registrationName` or `phasedRegistrationNames`.
96 *
97 * `executeDispatch` {function(object, function, string)}
98 * Optional, allows plugins to override how an event gets dispatched. By
99 * default, the listener is simply invoked.
100 *
101 * Each plugin that is injected into `EventsPluginHub` is immediately operable.
102 *
103 * @public
104 */
105var EventPluginHub = {
106 /**
107 * Methods for injecting dependencies.
108 */
109 injection: {
110 /**
111 * @param {array} InjectedEventPluginOrder
112 * @public
113 */
114 injectEventPluginOrder: EventPluginRegistry.injectEventPluginOrder,
115
116 /**
117 * @param {object} injectedNamesToPlugins Map from names to plugin modules.
118 */
119 injectEventPluginsByName: EventPluginRegistry.injectEventPluginsByName
120 },
121
122 /**
123 * Stores `listener` at `listenerBank[registrationName][key]`. Is idempotent.
124 *
125 * @param {object} inst The instance, which is the source of events.
126 * @param {string} registrationName Name of listener (e.g. `onClick`).
127 * @param {function} listener The callback to store.
128 */
129 putListener: function (inst, registrationName, listener) {
130 !(typeof listener === 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected %s listener to be a function, instead got type %s', registrationName, typeof listener) : _prodInvariant('94', registrationName, typeof listener) : void 0;
131
132 var key = getDictionaryKey(inst);
133 var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
134 bankForRegistrationName[key] = listener;
135
136 var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
137 if (PluginModule && PluginModule.didPutListener) {
138 PluginModule.didPutListener(inst, registrationName, listener);
139 }
140 },
141
142 /**
143 * @param {object} inst The instance, which is the source of events.
144 * @param {string} registrationName Name of listener (e.g. `onClick`).
145 * @return {?function} The stored callback.
146 */
147 getListener: function (inst, registrationName) {
148 // TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
149 // live here; needs to be moved to a better place soon
150 var bankForRegistrationName = listenerBank[registrationName];
151 if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
152 return null;
153 }
154 var key = getDictionaryKey(inst);
155 return bankForRegistrationName && bankForRegistrationName[key];
156 },
157
158 /**
159 * Deletes a listener from the registration bank.
160 *
161 * @param {object} inst The instance, which is the source of events.
162 * @param {string} registrationName Name of listener (e.g. `onClick`).
163 */
164 deleteListener: function (inst, registrationName) {
165 var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
166 if (PluginModule && PluginModule.willDeleteListener) {
167 PluginModule.willDeleteListener(inst, registrationName);
168 }
169
170 var bankForRegistrationName = listenerBank[registrationName];
171 // TODO: This should never be null -- when is it?
172 if (bankForRegistrationName) {
173 var key = getDictionaryKey(inst);
174 delete bankForRegistrationName[key];
175 }
176 },
177
178 /**
179 * Deletes all listeners for the DOM element with the supplied ID.
180 *
181 * @param {object} inst The instance, which is the source of events.
182 */
183 deleteAllListeners: function (inst) {
184 var key = getDictionaryKey(inst);
185 for (var registrationName in listenerBank) {
186 if (!listenerBank.hasOwnProperty(registrationName)) {
187 continue;
188 }
189
190 if (!listenerBank[registrationName][key]) {
191 continue;
192 }
193
194 var PluginModule = EventPluginRegistry.registrationNameModules[registrationName];
195 if (PluginModule && PluginModule.willDeleteListener) {
196 PluginModule.willDeleteListener(inst, registrationName);
197 }
198
199 delete listenerBank[registrationName][key];
200 }
201 },
202
203 /**
204 * Allows registered plugins an opportunity to extract events from top-level
205 * native browser events.
206 *
207 * @return {*} An accumulation of synthetic events.
208 * @internal
209 */
210 extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
211 var events;
212 var plugins = EventPluginRegistry.plugins;
213 for (var i = 0; i < plugins.length; i++) {
214 // Not every plugin in the ordering may be loaded at runtime.
215 var possiblePlugin = plugins[i];
216 if (possiblePlugin) {
217 var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
218 if (extractedEvents) {
219 events = accumulateInto(events, extractedEvents);
220 }
221 }
222 }
223 return events;
224 },
225
226 /**
227 * Enqueues a synthetic event that should be dispatched when
228 * `processEventQueue` is invoked.
229 *
230 * @param {*} events An accumulation of synthetic events.
231 * @internal
232 */
233 enqueueEvents: function (events) {
234 if (events) {
235 eventQueue = accumulateInto(eventQueue, events);
236 }
237 },
238
239 /**
240 * Dispatches all synthetic events on the event queue.
241 *
242 * @internal
243 */
244 processEventQueue: function (simulated) {
245 // Set `eventQueue` to null before processing it so that we can tell if more
246 // events get enqueued while processing.
247 var processingEventQueue = eventQueue;
248 eventQueue = null;
249 if (simulated) {
250 forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
251 } else {
252 forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
253 }
254 !!eventQueue ? process.env.NODE_ENV !== 'production' ? invariant(false, 'processEventQueue(): Additional events were enqueued while processing an event queue. Support for this has not yet been implemented.') : _prodInvariant('95') : void 0;
255 // This would be a good time to rethrow if any of the event handlers threw.
256 ReactErrorUtils.rethrowCaughtError();
257 },
258
259 /**
260 * These are needed for tests only. Do not use!
261 */
262 __purge: function () {
263 listenerBank = {};
264 },
265
266 __getListenerBank: function () {
267 return listenerBank;
268 }
269};
270
271module.exports = EventPluginHub;
\No newline at end of file