UNPKG

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