Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81152 views
1
/**
2
* Copyright 2013-2014, 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 &&
187
ReactBrowserEventEmitter.ReactEventListener.isEnabled()
188
);
189
},
190
191
/**
192
* We listen for bubbled touch events on the document object.
193
*
194
* Firefox v8.01 (and possibly others) exhibited strange behavior when
195
* mounting `onmousemove` events at some node that was not the document
196
* element. The symptoms were that if your mouse is not moving over something
197
* contained within that mount point (for example on the background) the
198
* top-level listeners for `onmousemove` won't be called. However, if you
199
* register the `mousemove` on the document object, then it will of course
200
* catch all `mousemove`s. This along with iOS quirks, justifies restricting
201
* top-level listeners to the document object only, at least for these
202
* movement types of events and possibly all events.
203
*
204
* @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
205
*
206
* Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
207
* they bubble to document.
208
*
209
* @param {string} registrationName Name of listener (e.g. `onClick`).
210
* @param {object} contentDocumentHandle Document which owns the container
211
*/
212
listenTo: function(registrationName, contentDocumentHandle) {
213
var mountAt = contentDocumentHandle;
214
var isListening = getListeningForDocument(mountAt);
215
var dependencies = EventPluginRegistry.
216
registrationNameDependencies[registrationName];
217
218
var topLevelTypes = EventConstants.topLevelTypes;
219
for (var i = 0, l = dependencies.length; i < l; i++) {
220
var dependency = dependencies[i];
221
if (!(
222
isListening.hasOwnProperty(dependency) &&
223
isListening[dependency]
224
)) {
225
if (dependency === topLevelTypes.topWheel) {
226
if (isEventSupported('wheel')) {
227
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
228
topLevelTypes.topWheel,
229
'wheel',
230
mountAt
231
);
232
} else if (isEventSupported('mousewheel')) {
233
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
234
topLevelTypes.topWheel,
235
'mousewheel',
236
mountAt
237
);
238
} else {
239
// Firefox needs to capture a different mouse scroll event.
240
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
241
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
242
topLevelTypes.topWheel,
243
'DOMMouseScroll',
244
mountAt
245
);
246
}
247
} else if (dependency === topLevelTypes.topScroll) {
248
249
if (isEventSupported('scroll', true)) {
250
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
251
topLevelTypes.topScroll,
252
'scroll',
253
mountAt
254
);
255
} else {
256
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
257
topLevelTypes.topScroll,
258
'scroll',
259
ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE
260
);
261
}
262
} else if (dependency === topLevelTypes.topFocus ||
263
dependency === topLevelTypes.topBlur) {
264
265
if (isEventSupported('focus', true)) {
266
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
267
topLevelTypes.topFocus,
268
'focus',
269
mountAt
270
);
271
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
272
topLevelTypes.topBlur,
273
'blur',
274
mountAt
275
);
276
} else if (isEventSupported('focusin')) {
277
// IE has `focusin` and `focusout` events which bubble.
278
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
279
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
280
topLevelTypes.topFocus,
281
'focusin',
282
mountAt
283
);
284
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
285
topLevelTypes.topBlur,
286
'focusout',
287
mountAt
288
);
289
}
290
291
// to make sure blur and focus event listeners are only attached once
292
isListening[topLevelTypes.topBlur] = true;
293
isListening[topLevelTypes.topFocus] = true;
294
} else if (topEventMapping.hasOwnProperty(dependency)) {
295
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
296
dependency,
297
topEventMapping[dependency],
298
mountAt
299
);
300
}
301
302
isListening[dependency] = true;
303
}
304
}
305
},
306
307
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
308
return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(
309
topLevelType,
310
handlerBaseName,
311
handle
312
);
313
},
314
315
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
316
return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(
317
topLevelType,
318
handlerBaseName,
319
handle
320
);
321
},
322
323
/**
324
* Listens to window scroll and resize events. We cache scroll values so that
325
* application code can access them without triggering reflows.
326
*
327
* NOTE: Scroll events do not bubble.
328
*
329
* @see http://www.quirksmode.org/dom/events/scroll.html
330
*/
331
ensureScrollValueMonitoring: function(){
332
if (!isMonitoringScrollValue) {
333
var refresh = ViewportMetrics.refreshScrollValues;
334
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
335
isMonitoringScrollValue = true;
336
}
337
},
338
339
eventNameDispatchConfigs: EventPluginHub.eventNameDispatchConfigs,
340
341
registrationNameModules: EventPluginHub.registrationNameModules,
342
343
putListener: EventPluginHub.putListener,
344
345
getListener: EventPluginHub.getListener,
346
347
deleteListener: EventPluginHub.deleteListener,
348
349
deleteAllListeners: EventPluginHub.deleteAllListeners
350
351
});
352
353
module.exports = ReactBrowserEventEmitter;
354
355