Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80529 views
1
/**
2
* Copyright 2013-2015, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ReactBrowserEventEmitter
10
* @typechecks static-only
11
*/
12
13
'use strict';
14
15
var EventConstants = require("./EventConstants");
16
var EventPluginHub = require("./EventPluginHub");
17
var EventPluginRegistry = require("./EventPluginRegistry");
18
var ReactEventEmitterMixin = require("./ReactEventEmitterMixin");
19
var ViewportMetrics = require("./ViewportMetrics");
20
21
var assign = require("./Object.assign");
22
var isEventSupported = require("./isEventSupported");
23
24
/**
25
* Summary of `ReactBrowserEventEmitter` event handling:
26
*
27
* - Top-level delegation is used to trap most native browser events. This
28
* may only occur in the main thread and is the responsibility of
29
* ReactEventListener, which is injected and can therefore support pluggable
30
* event sources. This is the only work that occurs in the main thread.
31
*
32
* - We normalize and de-duplicate events to account for browser quirks. This
33
* may be done in the worker thread.
34
*
35
* - Forward these native events (with the associated top-level type used to
36
* trap it) to `EventPluginHub`, which in turn will ask plugins if they want
37
* to extract any synthetic events.
38
*
39
* - The `EventPluginHub` will then process each event by annotating them with
40
* "dispatches", a sequence of listeners and IDs that care about that event.
41
*
42
* - The `EventPluginHub` then dispatches the events.
43
*
44
* Overview of React and the event system:
45
*
46
* +------------+ .
47
* | DOM | .
48
* +------------+ .
49
* | .
50
* v .
51
* +------------+ .
52
* | ReactEvent | .
53
* | Listener | .
54
* +------------+ . +-----------+
55
* | . +--------+|SimpleEvent|
56
* | . | |Plugin |
57
* +-----|------+ . v +-----------+
58
* | | | . +--------------+ +------------+
59
* | +-----------.--->|EventPluginHub| | Event |
60
* | | . | | +-----------+ | Propagators|
61
* | ReactEvent | . | | |TapEvent | |------------|
62
* | Emitter | . | |<---+|Plugin | |other plugin|
63
* | | . | | +-----------+ | utilities |
64
* | +-----------.--->| | +------------+
65
* | | | . +--------------+
66
* +-----|------+ . ^ +-----------+
67
* | . | |Enter/Leave|
68
* + . +-------+|Plugin |
69
* +-------------+ . +-----------+
70
* | application | .
71
* |-------------| .
72
* | | .
73
* | | .
74
* +-------------+ .
75
* .
76
* React Core . General Purpose Event Plugin System
77
*/
78
79
var alreadyListeningTo = {};
80
var isMonitoringScrollValue = false;
81
var reactTopListenersCounter = 0;
82
83
// For events like 'submit' which don't consistently bubble (which we trap at a
84
// lower node than `document`), binding at `document` would cause duplicate
85
// events so we don't include them here
86
var topEventMapping = {
87
topBlur: 'blur',
88
topChange: 'change',
89
topClick: 'click',
90
topCompositionEnd: 'compositionend',
91
topCompositionStart: 'compositionstart',
92
topCompositionUpdate: 'compositionupdate',
93
topContextMenu: 'contextmenu',
94
topCopy: 'copy',
95
topCut: 'cut',
96
topDoubleClick: 'dblclick',
97
topDrag: 'drag',
98
topDragEnd: 'dragend',
99
topDragEnter: 'dragenter',
100
topDragExit: 'dragexit',
101
topDragLeave: 'dragleave',
102
topDragOver: 'dragover',
103
topDragStart: 'dragstart',
104
topDrop: 'drop',
105
topFocus: 'focus',
106
topInput: 'input',
107
topKeyDown: 'keydown',
108
topKeyPress: 'keypress',
109
topKeyUp: 'keyup',
110
topMouseDown: 'mousedown',
111
topMouseMove: 'mousemove',
112
topMouseOut: 'mouseout',
113
topMouseOver: 'mouseover',
114
topMouseUp: 'mouseup',
115
topPaste: 'paste',
116
topScroll: 'scroll',
117
topSelectionChange: 'selectionchange',
118
topTextInput: 'textInput',
119
topTouchCancel: 'touchcancel',
120
topTouchEnd: 'touchend',
121
topTouchMove: 'touchmove',
122
topTouchStart: 'touchstart',
123
topWheel: 'wheel'
124
};
125
126
/**
127
* To ensure no conflicts with other potential React instances on the page
128
*/
129
var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2);
130
131
function getListeningForDocument(mountAt) {
132
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
133
// directly.
134
if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
135
mountAt[topListenersIDKey] = reactTopListenersCounter++;
136
alreadyListeningTo[mountAt[topListenersIDKey]] = {};
137
}
138
return alreadyListeningTo[mountAt[topListenersIDKey]];
139
}
140
141
/**
142
* `ReactBrowserEventEmitter` is used to attach top-level event listeners. For
143
* example:
144
*
145
* ReactBrowserEventEmitter.putListener('myID', 'onClick', myFunction);
146
*
147
* This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
148
*
149
* @internal
150
*/
151
var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
152
153
/**
154
* Injectable event backend
155
*/
156
ReactEventListener: null,
157
158
injection: {
159
/**
160
* @param {object} ReactEventListener
161
*/
162
injectReactEventListener: function(ReactEventListener) {
163
ReactEventListener.setHandleTopLevel(
164
ReactBrowserEventEmitter.handleTopLevel
165
);
166
ReactBrowserEventEmitter.ReactEventListener = ReactEventListener;
167
}
168
},
169
170
/**
171
* Sets whether or not any created callbacks should be enabled.
172
*
173
* @param {boolean} enabled True if callbacks should be enabled.
174
*/
175
setEnabled: function(enabled) {
176
if (ReactBrowserEventEmitter.ReactEventListener) {
177
ReactBrowserEventEmitter.ReactEventListener.setEnabled(enabled);
178
}
179
},
180
181
/**
182
* @return {boolean} True if callbacks are enabled.
183
*/
184
isEnabled: function() {
185
return !!(
186
(ReactBrowserEventEmitter.ReactEventListener && ReactBrowserEventEmitter.ReactEventListener.isEnabled())
187
);
188
},
189
190
/**
191
* We listen for bubbled touch events on the document object.
192
*
193
* Firefox v8.01 (and possibly others) exhibited strange behavior when
194
* mounting `onmousemove` events at some node that was not the document
195
* element. The symptoms were that if your mouse is not moving over something
196
* contained within that mount point (for example on the background) the
197
* top-level listeners for `onmousemove` won't be called. However, if you
198
* register the `mousemove` on the document object, then it will of course
199
* catch all `mousemove`s. This along with iOS quirks, justifies restricting
200
* top-level listeners to the document object only, at least for these
201
* movement types of events and possibly all events.
202
*
203
* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
204
*
205
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
206
* they bubble to document.
207
*
208
* @param {string} registrationName Name of listener (e.g. `onClick`).
209
* @param {object} contentDocumentHandle Document which owns the container
210
*/
211
listenTo: function(registrationName, contentDocumentHandle) {
212
var mountAt = contentDocumentHandle;
213
var isListening = getListeningForDocument(mountAt);
214
var dependencies = EventPluginRegistry.
215
registrationNameDependencies[registrationName];
216
217
var topLevelTypes = EventConstants.topLevelTypes;
218
for (var i = 0, l = dependencies.length; i < l; i++) {
219
var dependency = dependencies[i];
220
if (!(
221
(isListening.hasOwnProperty(dependency) && isListening[dependency])
222
)) {
223
if (dependency === topLevelTypes.topWheel) {
224
if (isEventSupported('wheel')) {
225
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
226
topLevelTypes.topWheel,
227
'wheel',
228
mountAt
229
);
230
} else if (isEventSupported('mousewheel')) {
231
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
232
topLevelTypes.topWheel,
233
'mousewheel',
234
mountAt
235
);
236
} else {
237
// Firefox needs to capture a different mouse scroll event.
238
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
239
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
240
topLevelTypes.topWheel,
241
'DOMMouseScroll',
242
mountAt
243
);
244
}
245
} else if (dependency === topLevelTypes.topScroll) {
246
247
if (isEventSupported('scroll', true)) {
248
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
249
topLevelTypes.topScroll,
250
'scroll',
251
mountAt
252
);
253
} else {
254
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
255
topLevelTypes.topScroll,
256
'scroll',
257
ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE
258
);
259
}
260
} else if (dependency === topLevelTypes.topFocus ||
261
dependency === topLevelTypes.topBlur) {
262
263
if (isEventSupported('focus', true)) {
264
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
265
topLevelTypes.topFocus,
266
'focus',
267
mountAt
268
);
269
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
270
topLevelTypes.topBlur,
271
'blur',
272
mountAt
273
);
274
} else if (isEventSupported('focusin')) {
275
// IE has `focusin` and `focusout` events which bubble.
276
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
277
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
278
topLevelTypes.topFocus,
279
'focusin',
280
mountAt
281
);
282
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
283
topLevelTypes.topBlur,
284
'focusout',
285
mountAt
286
);
287
}
288
289
// to make sure blur and focus event listeners are only attached once
290
isListening[topLevelTypes.topBlur] = true;
291
isListening[topLevelTypes.topFocus] = true;
292
} else if (topEventMapping.hasOwnProperty(dependency)) {
293
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
294
dependency,
295
topEventMapping[dependency],
296
mountAt
297
);
298
}
299
300
isListening[dependency] = true;
301
}
302
}
303
},
304
305
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
306
return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
307
topLevelType,
308
handlerBaseName,
309
handle
310
);
311
},
312
313
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
314
return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
315
topLevelType,
316
handlerBaseName,
317
handle
318
);
319
},
320
321
/**
322
* Listens to window scroll and resize events. We cache scroll values so that
323
* application code can access them without triggering reflows.
324
*
325
* NOTE: Scroll events do not bubble.
326
*
327
* @see http://www.quirksmode.org/dom/events/scroll.html
328
*/
329
ensureScrollValueMonitoring: function() {
330
if (!isMonitoringScrollValue) {
331
var refresh = ViewportMetrics.refreshScrollValues;
332
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
333
isMonitoringScrollValue = true;
334
}
335
},
336
337
eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs,
338
339
registrationNameModules: EventPluginHub.registrationNameModules,
340
341
putListener: EventPluginHub.putListener,
342
343
getListener: EventPluginHub.getListener,
344
345
deleteListener: EventPluginHub.deleteListener,
346
347
deleteAllListeners: EventPluginHub.deleteAllListeners
348
349
});
350
351
module.exports = ReactBrowserEventEmitter;
352
353