@samitouri / QOS-React-2 / commits / 88923f61a7
Fork 0

The library for web and native user interfaces.

Improve Browser Support for `wheel` Event

This improved browser support for the `wheel` event. - Try to use `wheel` event (DOM Level 3 Specification). - Fallback to `mousewheel` event. - Fallback to `DOMMouseWheel` (older Firefox). Also, since `wheel` is the standard event name, let's use that in React. NOTE: The tricky part was detecting if `wheel` is supported for IE9+ because `onwheel` does not exist. Test Plan: Execute the following in the console on a page with React: var React = require('React'); React.renderComponent(React.DOM.div({ style: { width: 10000, height: 10000 }, onWheel: function() { console.log('wheel'); } }, null), document.body); Verified that mousewheel events are logged to the console. Verified in IE8-10, Firefox, Chrome, and Safari.

CommitSyncScript committed Jun 6, 2013 at 14:44 UTC 88923f61a70b76b805e1ebc738a3fc3c0a6afe62
6 files changed +1371 -16
src/core/ReactEventEmitter.js
+13 -7
@@ -33,8 +33,8 @@ var isEventSupported = require('isEventSupported');
33 *
34 * - We trap low level 'top-level' events.
35 *
36 - * - We dedupe cross-browser event names into these 'top-level types' so that
37 - * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
36 + * - We dedupe cross-browser event names into these 'top-level types' (e.g. so
37 + * that `wheel`, `mousewheel`, and `DOMMouseScroll` fire one event).
38 *
39 * - At this point we have native browser events with the top-level type that
40 * was used to catch it at the top-level.
@@ -178,7 +178,6 @@ function listenAtTopLevel(touchNotMouse) {
178 trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
179 trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
180 trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
181 - trapBubbledEvent(topLevelTypes.topMouseWheel, 'mousewheel', mountAt);
181 if (touchNotMouse) {
182 trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
183 trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
@@ -196,10 +195,17 @@ function listenAtTopLevel(touchNotMouse) {
195 mountAt
196 );
197
199 - // Firefox needs to capture a different mouse scroll event.
200 - // @see http://www.quirksmode.org/dom/events/tests/scroll.html
201 - trapBubbledEvent(topLevelTypes.topMouseWheel, 'DOMMouseScroll', mountAt);
202 - // IE < 9 doesn't support capturing so just trap the bubbled event there.
198 + if (isEventSupported('wheel')) {
199 + trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
200 + } else if (isEventSupported('mousewheel')) {
201 + trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
202 + } else {
203 + // Firefox needs to capture a different mouse scroll event.
204 + // @see http://www.quirksmode.org/dom/events/tests/scroll.html
205 + trapBubbledEvent(topLevelTypes.topWheel, 'DOMMouseScroll', mountAt);
206 + }
207 +
208 + // IE<9 does not support capturing so just trap the bubbled event there.
209 if (isEventSupported('scroll', true)) {
210 trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
211 } else {
src/diff.diff new
+1337
@@ -0,0 +1,1337 @@
1 +diff --git a/static_upstream/react/core/ReactEventEmitter.js b/static_upstream/react/core/ReactEventEmitter.js
2 +index 0f392c1..b028ad4 100644
3 +--- a/static_upstream/react/core/ReactEventEmitter.js
4 ++++ b/static_upstream/react/core/ReactEventEmitter.js
5 +@@ -1,32 +1,43 @@
6 + /**
7 + * @providesModule ReactEventEmitter
8 ++ * @typechecks
9 + */
10 +
11 + "use strict";
12 +
13 + var BrowserEnv = require('BrowserEnv');
14 + var EventConstants = require('EventConstants');
15 ++var EventListener = require('EventListener');
16 + var EventPluginHub = require('EventPluginHub');
17 + var ExecutionEnvironment = require('ExecutionEnvironment');
18 +-var NormalizedEventListener = require('NormalizedEventListener');
19 +
20 + var invariant = require('invariant');
21 + var isEventSupported = require('isEventSupported');
22 +
23 +-var registrationNames = EventPluginHub.registrationNames;
24 +-var topLevelTypes = EventConstants.topLevelTypes;
25 +-var listen = NormalizedEventListener.listen;
26 +-var capture = NormalizedEventListener.capture;
27 +-
28 + /**
29 +- * `ReactEventEmitter` is used to attach top-level event listeners. For example:
30 ++ * Summary of `ReactEventEmitter` event handling:
31 + *
32 +- * ReactEventEmitter.putListener('myID', 'onClick', myFunction);
33 ++ * - We trap low level 'top-level' events.
34 ++ *
35 ++ * - We dedupe cross-browser event names into these 'top-level types' so that
36 ++ * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
37 ++ *
38 ++ * - At this point we have native browser events with the top-level type that
39 ++ * was used to catch it at the top-level.
40 ++ *
41 ++ * - We continuously stream these native events (and their respective top-level
42 ++ * types) to the event plugin system `EventPluginHub` and ask the plugin
43 ++ * system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
44 ++ * objects are the events that applications actually deal with - they are not
45 ++ * native browser events but cross-browser wrappers.
46 ++ *
47 ++ * - When returning the `AbstractEvent` objects, `EventPluginHub` will make
48 ++ * sure each abstract event is annotated with "dispatches", which are the
49 ++ * sequence of listeners (and IDs) that care about the event.
50 ++ *
51 ++ * - These `AbstractEvent` objects are fed back into the event plugin system,
52 ++ * which in turn executes these dispatches.
53 + *
54 +- * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
55 +- */
56 +-
57 +-/**
58 + * Overview of React and the event system:
59 + *
60 + * .
61 +@@ -58,36 +69,23 @@ var capture = NormalizedEventListener.capture;
62 + */
63 +
64 + /**
65 +- * We listen for bubbled touch events on the document object.
66 +- *
67 +- * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
68 +- * `onmousemove` events at some node that was not the document element. The
69 +- * symptoms were that if your mouse is not moving over something contained
70 +- * within that mount point (for example on the background) the top-level
71 +- * listeners for `onmousemove` won't be called. However, if you register the
72 +- * `mousemove` on the document object, then it will of course catch all
73 +- * `mousemove`s. This along with iOS quirks, justifies restricting top-level
74 +- * listeners to the document object only, at least for these movement types of
75 +- * events and possibly all events.
76 +- *
77 +- * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
78 +- *
79 +- * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
80 +- * they bubble to document.
81 +- *
82 +- * @see http://www.quirksmode.org/dom/events/keys.html.
83 ++ * Whether or not `ensureListening` has been invoked.
84 ++ * @type {boolean}
85 ++ * @private
86 + */
87 +-
88 + var _isListening = false;
89 +
90 + /**
91 +- * Traps top-level events that bubble. Delegates to the main dispatcher
92 +- * `handleTopLevel` after performing some basic normalization via
93 +- * `TopLevelCallbackCreator.createTopLevelCallback`.
94 ++ * Traps top-level events by using event bubbling.
95 ++ *
96 ++ * @param {string} topLevelType Record from `EventConstants`.
97 ++ * @param {string} handlerBaseName Event name (e.g. "click").
98 ++ * @param {DOMEventTarget} element Element on which to attach listener.
99 ++ * @internal
100 + */
101 +-function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
102 +- listen(
103 +- onWhat,
104 ++function trapBubbledEvent(topLevelType, handlerBaseName, element) {
105 ++ EventListener.listen(
106 ++ element,
107 + handlerBaseName,
108 + ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
109 + topLevelType
110 +@@ -97,10 +95,15 @@ function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
111 +
112 + /**
113 + * Traps a top-level event by using event capturing.
114 ++ *
115 ++ * @param {string} topLevelType Record from `EventConstants`.
116 ++ * @param {string} handlerBaseName Event name (e.g. "click").
117 ++ * @param {DOMEventTarget} element Element on which to attach listener.
118 ++ * @internal
119 + */
120 +-function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
121 +- capture(
122 +- onWhat,
123 ++function trapCapturedEvent(topLevelType, handlerBaseName, element) {
124 ++ EventListener.capture(
125 ++ element,
126 + handlerBaseName,
127 + ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
128 + topLevelType
129 +@@ -109,62 +112,51 @@ function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
130 + }
131 +
132 + /**
133 +- * Listens to document scroll and window resize events that may change the
134 +- * document scroll values. We store those results so as to discourage
135 +- * application code from asking the DOM itself which could trigger additional
136 +- * reflows.
137 ++ * Listens to window scroll and resize events. We cache scroll values so that
138 ++ * application code can access them without triggering reflows.
139 ++ *
140 ++ * NOTE: Scroll events do not bubble.
141 ++ *
142 ++ * @private
143 ++ * @see http://www.quirksmode.org/dom/events/scroll.html
144 + */
145 +-function registerDocumentScrollListener() {
146 +- listen(window, 'scroll', function(nativeEvent) {
147 +- if (nativeEvent.target === window) {
148 +- BrowserEnv.refreshAuthoritativeScrollValues();
149 +- }
150 +- });
151 +-}
152 +-
153 +-function registerDocumentResizeListener() {
154 +- listen(window, 'resize', function(nativeEvent) {
155 +- if (nativeEvent.target === window) {
156 +- BrowserEnv.refreshAuthoritativeScrollValues();
157 +- }
158 +- });
159 ++function registerScrollValueMonitoring() {
160 ++ var refresh = BrowserEnv.refreshAuthoritativeScrollValues;
161 ++ EventListener.listen(window, 'scroll', refresh);
162 ++ EventListener.listen(window, 'resize', refresh);
163 + }
164 +
165 + /**
166 +- * Summary of `ReactEventEmitter` event handling:
167 +- *
168 +- * - We trap low level 'top-level' events.
169 +- *
170 +- * - We dedupe cross-browser event names into these 'top-level types' so that
171 +- * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
172 +- *
173 +- * - At this point we have native browser events with the top-level type that
174 +- * was used to catch it at the top-level.
175 ++ * We listen for bubbled touch events on the document object.
176 + *
177 +- * - We continuously stream these native events (and their respective top-level
178 +- * types) to the event plugin system `EventPluginHub` and ask the plugin
179 +- * system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
180 +- * objects are the events that applications actually deal with - they are not
181 +- * native browser events but cross-browser wrappers.
182 ++ * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
183 ++ * `onmousemove` events at some node that was not the document element. The
184 ++ * symptoms were that if your mouse is not moving over something contained
185 ++ * within that mount point (for example on the background) the top-level
186 ++ * listeners for `onmousemove` won't be called. However, if you register the
187 ++ * `mousemove` on the document object, then it will of course catch all
188 ++ * `mousemove`s. This along with iOS quirks, justifies restricting top-level
189 ++ * listeners to the document object only, at least for these movement types of
190 ++ * events and possibly all events.
191 + *
192 +- * - When returning the `AbstractEvent` objects, `EventPluginHub` will make
193 +- * sure each abstract event is annotated with "dispatches", which are the
194 +- * sequence of listeners (and IDs) that care about the event.
195 ++ * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
196 + *
197 +- * - These `AbstractEvent` objects are fed back into the event plugin system,
198 +- * which in turn executes these dispatches.
199 ++ * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
200 ++ * they bubble to document.
201 + *
202 ++ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
203 + * @private
204 ++ * @see http://www.quirksmode.org/dom/events/keys.html.
205 + */
206 + function listenAtTopLevel(touchNotMouse) {
207 + invariant(
208 + !_isListening,
209 + 'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
210 + );
211 ++ var topLevelTypes = EventConstants.topLevelTypes;
212 + var mountAt = document;
213 +
214 +- registerDocumentScrollListener();
215 +- registerDocumentResizeListener();
216 ++ registerScrollValueMonitoring();
217 + trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
218 + trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
219 + trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
220 +@@ -212,82 +204,111 @@ function listenAtTopLevel(touchNotMouse) {
221 + }
222 +
223 + /**
224 +- * This is the heart of `ReactEventEmitter`. It simply streams the top-level
225 +- * native events to `EventPluginHub`.
226 ++ * `ReactEventEmitter` is used to attach top-level event listeners. For example:
227 ++ *
228 ++ * ReactEventEmitter.putListener('myID', 'onClick', myFunction);
229 ++ *
230 ++ * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
231 + *
232 +- * @param {object} topLevelType Record from `EventConstants`.
233 +- * @param {Event} nativeEvent A Standard Event with fixed `target` property.
234 +- * @param {DOMElement} renderedTarget Element of interest to the framework.
235 +- * @param {string} renderedTargetID string ID of `renderedTarget`.
236 + * @internal
237 + */
238 +-function handleTopLevel(
239 +- topLevelType,
240 +- nativeEvent,
241 +- renderedTargetID,
242 +- renderedTarget) {
243 +- var abstractEvents = EventPluginHub.extractAbstractEvents(
244 +- topLevelType,
245 +- nativeEvent,
246 +- renderedTargetID,
247 +- renderedTarget
248 +- );
249 ++var ReactEventEmitter = {
250 +
251 +- // The event queue being processed in the same cycle allows preventDefault.
252 +- EventPluginHub.enqueueAbstractEvents(abstractEvents);
253 +- EventPluginHub.processAbstractEventQueue();
254 +-}
255 ++ /**
256 ++ * React references `ReactEventTopLevelCallback` using this property in order
257 ++ * to allow dependency injection via `ensureListening`.
258 ++ */
259 ++ TopLevelCallbackCreator: null,
260 +
261 +-function setEnabled(enabled) {
262 +- invariant(
263 +- ExecutionEnvironment.canUseDOM,
264 +- 'setEnabled(...): Cannot toggle event listening in a Worker thread. This ' +
265 +- 'is likely a bug in the framework. Please report immediately.'
266 +- );
267 +- ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
268 +-}
269 ++ /**
270 ++ * Ensures that top-level event delegation listeners are installed.
271 ++ *
272 ++ * There are issues with listening to both touch events and mouse events on
273 ++ * the top-level, so we make the caller choose which one to listen to. (If
274 ++ * there's a touch top-level listeners, anchors don't receive clicks for some
275 ++ * reason, and only in some cases).
276 ++ *
277 ++ * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
278 ++ * @param {object} TopLevelCallbackCreator
279 ++ */
280 ++ ensureListening: function(touchNotMouse, TopLevelCallbackCreator) {
281 ++ invariant(
282 ++ ExecutionEnvironment.canUseDOM,
283 ++ 'ensureListening(...): Cannot toggle event listening in a Worker ' +
284 ++ 'thread. This is likely a bug in the framework. Please report ' +
285 ++ 'immediately.'
286 ++ );
287 ++ if (!_isListening) {
288 ++ ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
289 ++ listenAtTopLevel(touchNotMouse);
290 ++ _isListening = true;
291 ++ }
292 ++ },
293 +
294 +-function isEnabled() {
295 +- return ReactEventEmitter.TopLevelCallbackCreator.isEnabled();
296 +-}
297 ++ /**
298 ++ * Sets whether or not any created callbacks should be enabled.
299 ++ *
300 ++ * @param {boolean} enabled True if callbacks should be enabled.
301 ++ */
302 ++ setEnabled: function(enabled) {
303 ++ invariant(
304 ++ ExecutionEnvironment.canUseDOM,
305 ++ 'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
306 ++ 'This is likely a bug in the framework. Please report immediately.'
307 ++ );
308 ++ if (ReactEventEmitter.TopLevelCallbackCreator) {
309 ++ ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
310 ++ }
311 ++ },
312 +
313 +-/**
314 +- * Ensures that top-level event delegation listeners are listening at `mountAt`.
315 +- * There are issues with listening to both touch events and mouse events on the
316 +- * top-level, so we make the caller choose which one to listen to. (If there's a
317 +- * touch top-level listeners, anchors don't receive clicks for some reason, and
318 +- * only in some cases).
319 +- *
320 +- * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
321 +- * @param {object} TopLevelCallbackCreator Module that can create top-level
322 +- * callback handlers.
323 +- * @internal
324 +- */
325 +-function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
326 +- invariant(
327 +- ExecutionEnvironment.canUseDOM,
328 +- 'ensureListening(...): Cannot toggle event listening in a Worker thread. ' +
329 +- 'This is likely a bug in the framework. Please report immediately.'
330 +- );
331 +- if (!_isListening) {
332 +- ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
333 +- listenAtTopLevel(touchNotMouse);
334 +- _isListening = true;
335 +- }
336 +-}
337 ++ /**
338 ++ * @return {boolean} True if callbacks are enabled.
339 ++ */
340 ++ isEnabled: function() {
341 ++ return !!(
342 ++ ReactEventEmitter.TopLevelCallbackCreator &&
343 ++ ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
344 ++ );
345 ++ },
346 ++
347 ++ /**
348 ++ * Streams a fired top-level event to `EventPluginHub` where plugins have the
349 ++ * opportunity to create `ReactEvent`s to be dispatched.
350 ++ *
351 ++ * @param {string} topLevelType Record from `EventConstants`.
352 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
353 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
354 ++ * @param {object} nativeEvent Native browser event.
355 ++ */
356 ++ handleTopLevel: function(
357 ++ topLevelType,
358 ++ topLevelTarget,
359 ++ topLevelTargetID,
360 ++ nativeEvent) {
361 ++ var abstractEvents = EventPluginHub.extractAbstractEvents(
362 ++ topLevelType,
363 ++ topLevelTarget,
364 ++ topLevelTargetID,
365 ++ nativeEvent
366 ++ );
367 ++
368 ++ // Event queue being processed in the same cycle allows `preventDefault`.
369 ++ EventPluginHub.enqueueAbstractEvents(abstractEvents);
370 ++ EventPluginHub.processAbstractEventQueue();
371 ++ },
372 ++
373 ++ registrationNames: EventPluginHub.registrationNames,
374 +
375 +-var ReactEventEmitter = {
376 +- TopLevelCallbackCreator: null, // Injectable callback creator.
377 +- handleTopLevel: handleTopLevel,
378 +- setEnabled: setEnabled,
379 +- isEnabled: isEnabled,
380 +- ensureListening: ensureListening,
381 +- registrationNames: registrationNames,
382 + putListener: EventPluginHub.putListener,
383 ++
384 + getListener: EventPluginHub.getListener,
385 ++
386 + deleteAllListeners: EventPluginHub.deleteAllListeners,
387 ++
388 + trapBubbledEvent: trapBubbledEvent,
389 ++
390 + trapCapturedEvent: trapCapturedEvent
391 ++
392 + };
393 +
394 + module.exports = ReactEventEmitter;
395 +diff --git a/static_upstream/react/core/ReactEventTopLevelCallback.js b/static_upstream/react/core/ReactEventTopLevelCallback.js
396 +index e3e3c52..f60e30f 100644
397 +--- a/static_upstream/react/core/ReactEventTopLevelCallback.js
398 ++++ b/static_upstream/react/core/ReactEventTopLevelCallback.js
399 +@@ -1,5 +1,6 @@
400 + /**
401 + * @providesModule ReactEventTopLevelCallback
402 ++ * @typechecks
403 + */
404 +
405 + "use strict";
406 +@@ -9,51 +10,59 @@ var ReactEventEmitter = require('ReactEventEmitter');
407 + var ReactInstanceHandles = require('ReactInstanceHandles');
408 +
409 + var getDOMNodeID = require('getDOMNodeID');
410 ++var getEventTarget = require('getEventTarget');
411 +
412 ++/**
413 ++ * @type {boolean}
414 ++ * @private
415 ++ */
416 + var _topLevelListenersEnabled = true;
417 +
418 ++/**
419 ++ * Top-level callback creator used to implement event handling using delegation.
420 ++ * This is used via dependency injection in `ReactEventEmitter.ensureListening`.
421 ++ */
422 + var ReactEventTopLevelCallback = {
423 +
424 + /**
425 +- * @param {boolean} enabled Whether or not all callbacks that have ever been
426 +- * created with this module should be enabled.
427 ++ * Sets whether or not any created callbacks should be enabled.
428 ++ *
429 ++ * @param {boolean} enabled True if callbacks should be enabled.
430 + */
431 + setEnabled: function(enabled) {
432 + _topLevelListenersEnabled = !!enabled;
433 + },
434 +
435 ++ /**
436 ++ * @return {boolean} True if callbacks are enabled.
437 ++ */
438 + isEnabled: function() {
439 + return _topLevelListenersEnabled;
440 + },
441 +
442 + /**
443 +- * For a given `topLevelType`, creates a callback that could be added as a
444 +- * listener to the document. That top level callback will simply fix the
445 +- * native events before invoking `handleTopLevel`.
446 ++ * Creates a callback for the supplied `topLevelType` that could be added as
447 ++ * a listener to the document. The callback computes a `topLevelTarget` which
448 ++ * should be the root node of a mounted React component where the listener
449 ++ * is attached.
450 + *
451 +- * - Raw native events cannot be trusted to describe their targets correctly
452 +- * so we expect that the argument to the nested function has already been
453 +- * fixed. But the `target` property may not be something of interest to
454 +- * React, so we find the most suitable target. But even at that point, DOM
455 +- * Elements (the target ) can't be trusted to describe their IDs correctly
456 +- * so we obtain the ID in a reliable manner and pass it to
457 +- * `handleTopLevel`. The target/id that we found to be relevant to our
458 +- * framework are called `renderedTarget`/`renderedTargetID` respectively.
459 ++ * @param {string} topLevelType Record from `EventConstants`.
460 ++ * @return {function} Callback for handling top-level events.
461 + */
462 + createTopLevelCallback: function(topLevelType) {
463 +- return function(fixedNativeEvent) {
464 ++ return function(nativeEvent) {
465 + if (!_topLevelListenersEnabled) {
466 + return;
467 + }
468 +- var renderedTarget = ReactInstanceHandles.getFirstReactDOM(
469 +- fixedNativeEvent.target
470 ++ var topLevelTarget = ReactInstanceHandles.getFirstReactDOM(
471 ++ getEventTarget(nativeEvent)
472 + ) || ExecutionEnvironment.global;
473 +- var renderedTargetID = getDOMNodeID(renderedTarget);
474 ++ var topLevelTargetID = getDOMNodeID(topLevelTarget) || '';
475 + ReactEventEmitter.handleTopLevel(
476 + topLevelType,
477 +- fixedNativeEvent,
478 +- renderedTargetID,
479 +- renderedTarget
480 ++ topLevelTarget,
481 ++ topLevelTargetID,
482 ++ nativeEvent
483 + );
484 + };
485 + }
486 +diff --git a/static_upstream/react/core/ReactInstanceHandles.js b/static_upstream/react/core/ReactInstanceHandles.js
487 +index 4eaf0e3..948ebcb 100644
488 +--- a/static_upstream/react/core/ReactInstanceHandles.js
489 ++++ b/static_upstream/react/core/ReactInstanceHandles.js
490 +@@ -1,5 +1,6 @@
491 + /**
492 + * @providesModule ReactInstanceHandles
493 ++ * @typechecks
494 + */
495 +
496 + "use strict";
497 +@@ -43,13 +44,13 @@ function isValidID(id) {
498 + /**
499 + * True if the supplied `node` is rendered by React.
500 + *
501 +- * @param {DOMElement} node DOM Element to check.
502 ++ * @param {DOMEventTarget} node DOM Element to check.
503 + * @return {boolean} True if the DOM Element appears to be rendered by React.
504 + * @private
505 + */
506 + function isRenderedByReact(node) {
507 + var id = getDOMNodeID(node);
508 +- return id && id.charAt(0) === SEPARATOR;
509 ++ return id ? id.charAt(0) === SEPARATOR : false;
510 + }
511 +
512 + /**
513 +@@ -126,8 +127,8 @@ var ReactInstanceHandles = {
514 + * Traverses up the ancestors of the supplied node to find a node that is a
515 + * DOM representation of a React component.
516 + *
517 +- * @param {DOMElement} node
518 +- * @return {?DOMElement}
519 ++ * @param {?DOMEventTarget} node
520 ++ * @return {?DOMEventTarget}
521 + * @internal
522 + */
523 + getFirstReactDOM: function(node) {
524 +@@ -145,9 +146,9 @@ var ReactInstanceHandles = {
525 + * Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
526 + * Exploits the ID naming scheme to perform the search quickly.
527 + *
528 +- * @param {DOMElement} ancestorNode Search from this root.
529 ++ * @param {DOMEventTarget} ancestorNode Search from this root.
530 + * @pararm {string} id ID of the DOM representation of the component.
531 +- * @return {?DOMElement} DOM element with the supplied `id`, if one exists.
532 ++ * @return {?DOMEventTarget} DOM node with the supplied `id`, if one exists.
533 + * @internal
534 + */
535 + findComponentRoot: function(ancestorNode, id) {
536 +@@ -214,7 +215,7 @@ var ReactInstanceHandles = {
537 + * contains the React component with the supplied DOM ID.
538 + *
539 + * @param {string} id DOM ID of a React component.
540 +- * @return {string} DOM ID of the React component that is the root.
541 ++ * @return {?string} DOM ID of the React component that is the root.
542 + * @internal
543 + */
544 + getReactRootIDFromNodeID: function(id) {
545 +diff --git a/static_upstream/react/dom/getDOMNodeID.js b/static_upstream/react/dom/getDOMNodeID.js
546 +index 503f752..738239a 100644
547 +--- a/static_upstream/react/dom/getDOMNodeID.js
548 ++++ b/static_upstream/react/dom/getDOMNodeID.js
549 +@@ -1,5 +1,6 @@
550 + /**
551 + * @providesModule getDOMNodeID
552 ++ * @typechecks
553 + */
554 +
555 + "use strict";
556 +@@ -9,8 +10,8 @@
557 + * control whose name or ID is "id". However, not all DOM nodes support
558 + * `getAttributeNode` (document - which is not a form) so that is checked first.
559 + *
560 +- * @param {Element} domNode DOM node element to return ID of.
561 +- * @returns {string} The ID of `domNode`.
562 ++ * @param {DOMElement|DOMWindow|DOMDocument} domNode DOM node.
563 ++ * @returns {string} ID of the supplied `domNode`.
564 + */
565 + function getDOMNodeID(domNode) {
566 + if (domNode.getAttributeNode) {
567 +diff --git a/static_upstream/react/dom/getEventTarget.js b/static_upstream/react/dom/getEventTarget.js
568 +new file mode 100644
569 +index 0000000..d9337fd
570 +--- /dev/null
571 ++++ b/static_upstream/react/dom/getEventTarget.js
572 +@@ -0,0 +1,25 @@
573 ++/**
574 ++ * @providesModule getEventTarget
575 ++ * @typechecks
576 ++ */
577 ++
578 ++var ExecutionEnvironment = require('ExecutionEnvironment');
579 ++
580 ++/**
581 ++ * Gets the target node from a native browser event by accounting for
582 ++ * inconsistencies in browser DOM APIs.
583 ++ *
584 ++ * @param {object} nativeEvent Native browser event.
585 ++ * @return {DOMEventTarget} Target node.
586 ++ */
587 ++function getEventTarget(nativeEvent) {
588 ++ var target =
589 ++ nativeEvent.target ||
590 ++ nativeEvent.srcElement ||
591 ++ ExecutionEnvironment.global;
592 ++ // Safari may fire events on text nodes (Node.TEXT_NODE is 3).
593 ++ // @see http://www.quirksmode.org/js/events_properties.html
594 ++ return target.nodeType === 3 ? target.parentNode : target;
595 ++}
596 ++
597 ++module.exports = getEventTarget;
598 +diff --git a/static_upstream/react/event/AbstractEvent.js b/static_upstream/react/event/AbstractEvent.js
599 +index 444031f..3e59b9f 100644
600 +--- a/static_upstream/react/event/AbstractEvent.js
601 ++++ b/static_upstream/react/event/AbstractEvent.js
602 +@@ -30,14 +30,12 @@ var MAX_POOL_SIZE = 20;
603 + * unreliable native event.
604 + */
605 + function AbstractEvent(
606 +- abstractEventType,
607 +- abstractTargetID, // Allows the abstract target to differ from native.
608 +- originatingTopLevelEventType,
609 ++ reactEventType,
610 ++ reactTargetID, // Allows the abstract target to differ from native.
611 + nativeEvent,
612 + data) {
613 +- this.type = abstractEventType;
614 +- this.abstractTargetID = abstractTargetID || '';
615 +- this.originatingTopLevelEventType = originatingTopLevelEventType;
616 ++ this.reactEventType = reactEventType;
617 ++ this.reactTargetID = reactTargetID || '';
618 + this.nativeEvent = nativeEvent;
619 + this.data = data;
620 + // TODO: Deprecate storing target - doesn't always make sense for some types
621 +@@ -249,12 +247,10 @@ AbstractEvent.persistentCloneOf = function(abstractEvent) {
622 + throwIf(!(abstractEvent instanceof AbstractEvent), CLONE_TYPE_ERR);
623 + }
624 + return new AbstractEvent(
625 +- abstractEvent.type,
626 +- abstractEvent.abstractTargetID,
627 +- abstractEvent.originatingTopLevelEventType,
628 ++ abstractEvent.reactEventType,
629 ++ abstractEvent.reactTargetID,
630 + abstractEvent.nativeEvent,
631 +- abstractEvent.data,
632 +- abstractEvent.target
633 ++ abstractEvent.data
634 + );
635 + };
636 +
637 +diff --git a/static_upstream/react/event/EventPluginHub.js b/static_upstream/react/event/EventPluginHub.js
638 +index 85cd51e..7092947 100644
639 +--- a/static_upstream/react/event/EventPluginHub.js
640 ++++ b/static_upstream/react/event/EventPluginHub.js
641 +@@ -180,15 +180,16 @@ function recordAllRegistrationNames(eventType, PluginModule) {
642 + * @param {AbstractEvent} abstractEvent to look at
643 + */
644 + function getPluginModuleForAbstractEvent(abstractEvent) {
645 +- if (abstractEvent.type.registrationName) {
646 +- return registrationNames[abstractEvent.type.registrationName];
647 ++ var reactEventType = abstractEvent.reactEventType;
648 ++ if (reactEventType.registrationName) {
649 ++ return registrationNames[reactEventType.registrationName];
650 + } else {
651 +- for (var phase in abstractEvent.type.phasedRegistrationNames) {
652 +- if (!abstractEvent.type.phasedRegistrationNames.hasOwnProperty(phase)) {
653 ++ for (var phase in reactEventType.phasedRegistrationNames) {
654 ++ if (!reactEventType.phasedRegistrationNames.hasOwnProperty(phase)) {
655 + continue;
656 + }
657 + var PluginModule = registrationNames[
658 +- abstractEvent.type.phasedRegistrationNames[phase]
659 ++ reactEventType.phasedRegistrationNames[phase]
660 + ];
661 + if (PluginModule) {
662 + return PluginModule;
663 +@@ -209,36 +210,36 @@ var deleteAllListeners = function(domID) {
664 + * Accepts the stream of top level native events, and gives every registered
665 + * plugin an opportunity to extract `AbstractEvent`s with annotated dispatches.
666 + *
667 +- * @param {Enum} topLevelType Record from `EventConstants`.
668 +- * @param {Event} nativeEvent A Standard Event with fixed `target` property.
669 +- * @param {Element} renderedTarget Element of interest to the framework, usually
670 +- * the same as `nativeEvent.target` but occasionally an element immediately
671 +- * above `nativeEvent.target` (the first DOM node recognized as one "rendered"
672 +- * by the framework at hand.)
673 +- * @param {string} renderedTargetID string ID of `renderedTarget`.
674 ++ * @param {string} topLevelType Record from `EventConstants`.
675 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
676 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
677 ++ * @param {object} nativeEvent Native browser event.
678 ++ * @return {*} An accumulation of `AbstractEvent`s.
679 + */
680 +-var extractAbstractEvents =
681 +- function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
682 +- var abstractEvents;
683 +- var plugins = injection.plugins;
684 +- var len = plugins.length;
685 +- for (var i = 0; i < len; i++) {
686 +- // Not every plugin in the ordering may be loaded at runtime.
687 +- var possiblePlugin = plugins[i];
688 +- var extractedAbstractEvents =
689 +- possiblePlugin &&
690 +- possiblePlugin.extractAbstractEvents(
691 +- topLevelType,
692 +- nativeEvent,
693 +- renderedTargetID,
694 +- renderedTarget
695 +- );
696 ++var extractAbstractEvents = function(
697 ++ topLevelType,
698 ++ topLevelTarget,
699 ++ topLevelTargetID,
700 ++ nativeEvent) {
701 ++ var abstractEvents;
702 ++ var plugins = injection.plugins;
703 ++ for (var i = 0, l = plugins.length; i < l; i++) {
704 ++ // Not every plugin in the ordering may be loaded at runtime.
705 ++ var possiblePlugin = plugins[i];
706 ++ if (possiblePlugin) {
707 ++ var extractedAbstractEvents = possiblePlugin.extractAbstractEvents(
708 ++ topLevelType,
709 ++ topLevelTarget,
710 ++ topLevelTargetID,
711 ++ nativeEvent
712 ++ );
713 + if (extractedAbstractEvents) {
714 + abstractEvents = accumulate(abstractEvents, extractedAbstractEvents);
715 + }
716 + }
717 +- return abstractEvents;
718 +- };
719 ++ }
720 ++ return abstractEvents;
721 ++};
722 +
723 + var enqueueAbstractEvents = function(abstractEvents) {
724 + if (abstractEvents) {
725 +diff --git a/static_upstream/react/event/EventPropagators.js b/static_upstream/react/event/EventPropagators.js
726 +index 17c80f4..3d9103c 100644
727 +--- a/static_upstream/react/event/EventPropagators.js
728 ++++ b/static_upstream/react/event/EventPropagators.js
729 +@@ -44,7 +44,7 @@ var injection = {
730 + */
731 + function listenerAtPhase(id, abstractEvent, propagationPhase) {
732 + var registrationName =
733 +- abstractEvent.type.phasedRegistrationNames[propagationPhase];
734 ++ abstractEvent.reactEventType.phasedRegistrationNames[propagationPhase];
735 + return getListener(id, registrationName);
736 + }
737 +
738 +@@ -78,9 +78,9 @@ function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
739 + * have a different target.
740 + */
741 + function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
742 +- if (abstractEvent && abstractEvent.type.phasedRegistrationNames) {
743 ++ if (abstractEvent && abstractEvent.reactEventType.phasedRegistrationNames) {
744 + injection.InstanceHandle.traverseTwoPhase(
745 +- abstractEvent.abstractTargetID,
746 ++ abstractEvent.reactTargetID,
747 + accumulateDirectionalDispatches,
748 + abstractEvent
749 + );
750 +@@ -91,11 +91,12 @@ function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
751 + /**
752 + * Accumulates without regard to direction, does not look for phased
753 + * registration names. Same as `accumulateDirectDispatchesSingle` but without
754 +- * requiring that the `abstractTargetID` be the same as the dispatched ID.
755 ++ * requiring that the `reactTargetID` be the same as the dispatched ID.
756 + */
757 + function accumulateDispatches(id, ignoredDirection, abstractEvent) {
758 +- if (abstractEvent && abstractEvent.type.registrationName) {
759 +- var listener = getListener(id, abstractEvent.type.registrationName);
760 ++ if (abstractEvent && abstractEvent.reactEventType.registrationName) {
761 ++ var registrationName = abstractEvent.reactEventType.registrationName;
762 ++ var listener = getListener(id, registrationName);
763 + if (listener) {
764 + abstractEvent._dispatchListeners =
765 + accumulate(abstractEvent._dispatchListeners, listener);
766 +@@ -106,12 +107,12 @@ function accumulateDispatches(id, ignoredDirection, abstractEvent) {
767 +
768 + /**
769 + * Accumulates dispatches on an `AbstractEvent`, but only for the
770 +- * `abstractTargetID`.
771 ++ * `reactTargetID`.
772 + * @param {AbstractEvent} abstractEvent
773 + */
774 + function accumulateDirectDispatchesSingle(abstractEvent) {
775 +- if (abstractEvent && abstractEvent.type.registrationName) {
776 +- accumulateDispatches(abstractEvent.abstractTargetID, null, abstractEvent);
777 ++ if (abstractEvent && abstractEvent.reactEventType.registrationName) {
778 ++ accumulateDispatches(abstractEvent.reactTargetID, null, abstractEvent);
779 + }
780 + }
781 +
782 +diff --git a/static_upstream/react/event/NormalizedEventListener.js b/static_upstream/react/event/NormalizedEventListener.js
783 +deleted file mode 100644
784 +index 5506fe7..0000000
785 +--- a/static_upstream/react/event/NormalizedEventListener.js
786 ++++ /dev/null
787 +@@ -1,70 +0,0 @@
788 +-/**
789 +- * @providesModule NormalizedEventListener
790 +- */
791 +-
792 +-var EventListener = require('EventListener');
793 +-
794 +-/**
795 +- * @param {?Event} eventParam Event parameter from an attached listener.
796 +- * @return {Event} Normalized event object.
797 +- * @private
798 +- */
799 +-function normalizeEvent(eventParam) {
800 +- var nativeEvent = eventParam || window.event;
801 +- // In some browsers (OLD FF), setting the target throws an error. A good way
802 +- // to tell if setting the target will throw an error, is to check if the event
803 +- // has a `target` property. Safari events have a `target` but it's not always
804 +- // normalized. Even if a `target` property exists, it's good to only set the
805 +- // target property if we realize that a change will actually take place.
806 +- var hasTargetProperty = 'target' in nativeEvent;
807 +- var eventTarget = nativeEvent.target || nativeEvent.srcElement || window;
808 +- // Safari may fire events on text nodes (Node.TEXT_NODE is 3)
809 +- // @see http://www.quirksmode.org/js/events_properties.html
810 +- var textNodeNormalizedTarget =
811 +- (eventTarget.nodeType === 3) ? eventTarget.parentNode : eventTarget;
812 +- if (!hasTargetProperty || nativeEvent.target !== textNodeNormalizedTarget) {
813 +- // TODO: Normalize the object via `merge()` to work with strict mode.
814 +- nativeEvent.target = textNodeNormalizedTarget;
815 +- }
816 +- return nativeEvent;
817 +-}
818 +-
819 +-function createNormalizedCallback(cb) {
820 +- return function(unfixedNativeEvent) {
821 +- cb(normalizeEvent(unfixedNativeEvent));
822 +- };
823 +-}
824 +-
825 +-var NormalizedEventListener = {
826 +-
827 +- /**
828 +- * Listens to bubbled events on a DOM node.
829 +- *
830 +- * NOTE: The listener will be invoked with a normalized event object.
831 +- *
832 +- * @param {DOMElement} el DOM element to register listener on.
833 +- * @param {string} handlerBaseName Event name, e.g. "click".
834 +- * @param {function} cb Callback function.
835 +- * @public
836 +- */
837 +- listen: function(el, handlerBaseName, cb) {
838 +- EventListener.listen(el, handlerBaseName, createNormalizedCallback(cb));
839 +- },
840 +-
841 +- /**
842 +- * Listens to captured events on a DOM node.
843 +- *
844 +- * NOTE: The listener will be invoked with a normalized event object.
845 +- *
846 +- * @param {DOMElement} el DOM element to register listener on.
847 +- * @param {string} handlerBaseName Event name, e.g. "click".
848 +- * @param {function} cb Callback function.
849 +- * @public
850 +- */
851 +- capture: function(el, handlerBaseName, cb) {
852 +- EventListener.capture(el, handlerBaseName, createNormalizedCallback(cb));
853 +- }
854 +-
855 +-};
856 +-
857 +-module.exports = NormalizedEventListener;
858 +diff --git a/static_upstream/react/eventPlugins/AnalyticsEventPluginFactory.js b/static_upstream/react/eventPlugins/AnalyticsEventPluginFactory.js
859 +index e1c4f38..8508047 100644
860 +--- a/static_upstream/react/eventPlugins/AnalyticsEventPluginFactory.js
861 ++++ b/static_upstream/react/eventPlugins/AnalyticsEventPluginFactory.js
862 +@@ -122,20 +122,25 @@ if (__DEV__) {
863 + * This plugin does not really extract any abstract events. Rather it just looks
864 + * at the top level event and bumps up counters as appropriate
865 + *
866 +- * @see EventPluginHub.extractAbstractEvents
867 ++ * @param {string} topLevelType Record from `EventConstants`.
868 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
869 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
870 ++ * @param {object} nativeEvent Native browser event.
871 ++ * @return {*} An accumulation of `AbstractEvent`s.
872 ++ * @see {EventPluginHub.extractAbstractEvents}
873 + */
874 + function extractAbstractEvents(
875 + topLevelType,
876 +- nativeEvent,
877 +- renderedTargetID,
878 +- renderedTarget) {
879 ++ topLevelTarget,
880 ++ topLevelTargetID,
881 ++ nativeEvent) {
882 + var currentEvent = topLevelTypesToAnalyticsEvent[topLevelType];
883 +- if (!currentEvent || !renderedTarget || !renderedTarget.attributes) {
884 ++ if (!currentEvent || !topLevelTarget || !topLevelTarget.attributes) {
885 + return null;
886 + }
887 +
888 +- var analyticsIDAttribute = renderedTarget.attributes[ANALYTICS_ID];
889 +- var analyticsEventsAttribute = renderedTarget.attributes[ANALYTICS_EVENTS];
890 ++ var analyticsIDAttribute = topLevelTarget.attributes[ANALYTICS_ID];
891 ++ var analyticsEventsAttribute = topLevelTarget.attributes[ANALYTICS_EVENTS];
892 + if(!analyticsIDAttribute || !analyticsEventsAttribute) {
893 + return null;
894 + }
895 +diff --git a/static_upstream/react/eventPlugins/EnterLeaveEventPlugin.js b/static_upstream/react/eventPlugins/EnterLeaveEventPlugin.js
896 +index 856b1d7..7a00e71 100644
897 +--- a/static_upstream/react/eventPlugins/EnterLeaveEventPlugin.js
898 ++++ b/static_upstream/react/eventPlugins/EnterLeaveEventPlugin.js
899 +@@ -1,5 +1,6 @@
900 + /**
901 + * @providesModule EnterLeaveEventPlugin
902 ++ * @typechecks
903 + */
904 +
905 + "use strict";
906 +@@ -22,28 +23,32 @@ var abstractEventTypes = {
907 + };
908 +
909 + /**
910 +- * For almost every interaction we care about, there will be a top level
911 +- * `mouseOver` and `mouseOut` event that occur so we can usually only pay
912 +- * attention to one of the two (we'll pay attention to the `mouseOut` event) to
913 +- * avoid extracting a duplicate event. However, there's one interaction where
914 +- * there will be no `mouseOut` event to rely on - mousing from outside the
915 +- * browser *into* the chrome. We detect this scenario and only in that case, we
916 +- * use the `mouseOver` event.
917 ++ * For almost every interaction we care about, there will be a top-level
918 ++ * `mouseover` and `mouseout` event that occurs so only pay attention to one of
919 ++ * the two (to avoid duplicate events). We use the `mouseout` event.
920 + *
921 +- * @see EventPluginHub.extractAbstractEvents
922 ++ * However, there's one interaction where there will be no `mouseout` event to
923 ++ * rely on - mousing from outside the browser *into* the chrome. We detect this
924 ++ * scenario and only in that case, we use the `mouseover` event.
925 ++ *
926 ++ * @param {string} topLevelType Record from `EventConstants`.
927 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
928 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
929 ++ * @param {object} nativeEvent Native browser event.
930 ++ * @return {*} An accumulation of `AbstractEvent`s.
931 ++ * @see {EventPluginHub.extractAbstractEvents}
932 + */
933 + var extractAbstractEvents = function(
934 + topLevelType,
935 +- nativeEvent,
936 +- renderedTargetID,
937 +- renderedTarget) {
938 +-
939 ++ topLevelTarget,
940 ++ topLevelTargetID,
941 ++ nativeEvent) {
942 + if (topLevelType === topLevelTypes.topMouseOver &&
943 + (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
944 +- return;
945 ++ return null;
946 + }
947 + if (topLevelType !== topLevelTypes.topMouseOut &&
948 +- topLevelType !== topLevelTypes.topMouseOver){
949 ++ topLevelType !== topLevelTypes.topMouseOver) {
950 + return null; // Must not be a mouse in or mouse out - ignoring.
951 + }
952 +
953 +@@ -51,32 +56,33 @@ var extractAbstractEvents = function(
954 + if (topLevelType === topLevelTypes.topMouseOut) {
955 + to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
956 + ExecutionEnvironment.global;
957 +- from = renderedTarget;
958 ++ from = topLevelTarget;
959 + } else {
960 +- to = renderedTarget;
961 ++ to = topLevelTarget;
962 + from = ExecutionEnvironment.global;
963 + }
964 +
965 + // Nothing pertains to our managed components.
966 +- if (from === to ) {
967 +- return;
968 ++ if (from === to) {
969 ++ return null;
970 + }
971 +
972 + var fromID = from ? getDOMNodeID(from) : '';
973 + var toID = to ? getDOMNodeID(to) : '';
974 ++
975 + var leave = AbstractEvent.getPooled(
976 + abstractEventTypes.mouseLeave,
977 + fromID,
978 +- topLevelType,
979 + nativeEvent
980 + );
981 + var enter = AbstractEvent.getPooled(
982 + abstractEventTypes.mouseEnter,
983 + toID,
984 +- topLevelType,
985 + nativeEvent
986 + );
987 ++
988 + EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
989 ++
990 + return [leave, enter];
991 + };
992 +
993 +diff --git a/static_upstream/react/eventPlugins/ResponderEventPlugin.js b/static_upstream/react/eventPlugins/ResponderEventPlugin.js
994 +index 4861cad..2e6b557 100644
995 +--- a/static_upstream/react/eventPlugins/ResponderEventPlugin.js
996 ++++ b/static_upstream/react/eventPlugins/ResponderEventPlugin.js
997 +@@ -148,14 +148,13 @@ var abstractEventTypes = {
998 + */
999 +
1000 + /**
1001 +- * @param {TopLevelTypes} topLevelType Top level event type being examined.
1002 +- * @param {DOMEvent} nativeEvent Native DOM event.
1003 ++ * @param {string} topLevelType Record from `EventConstants`.
1004 + * @param {string} renderedTargetID ID of deepest React rendered element.
1005 +- *
1006 +- * @return {Accumulation<AbstractEvent>} Extracted events.
1007 ++ * @param {object} nativeEvent Native browser event.
1008 ++ * @return {*} An accumulation of extracted `AbstractEvent`s.
1009 + */
1010 + var setResponderAndExtractTransfer =
1011 +- function(topLevelType, nativeEvent, renderedTargetID) {
1012 ++ function(topLevelType, renderedTargetID, nativeEvent) {
1013 + var type;
1014 + var shouldSetEventType =
1015 + isStartish(topLevelType) ? abstractEventTypes.startShouldSetResponder :
1016 +@@ -221,7 +220,6 @@ var setResponderAndExtractTransfer =
1017 + return extracted;
1018 + };
1019 +
1020 +-
1021 + /**
1022 + * A transfer is a negotiation between a currently set responder and the next
1023 + * element to claim responder status. Any start event could trigger a transfer
1024 +@@ -239,51 +237,60 @@ function canTriggerTransfer(topLevelType) {
1025 + (isPressing && isMoveish(topLevelType));
1026 + }
1027 +
1028 +-var extractAbstractEvents =
1029 +- function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
1030 +- var extracted;
1031 +- // Must have missed an end event - reset the state here.
1032 +- if (responderID && isStartish(topLevelType)) {
1033 +- responderID = null;
1034 +- }
1035 +- if (isStartish(topLevelType)) {
1036 +- isPressing = true;
1037 +- } else if (isEndish(topLevelType)) {
1038 +- isPressing = false;
1039 +- }
1040 +- if (canTriggerTransfer(topLevelType)) {
1041 +- var transfer = setResponderAndExtractTransfer(
1042 +- topLevelType,
1043 +- nativeEvent,
1044 +- renderedTargetID,
1045 +- renderedTarget
1046 +- );
1047 +- if (transfer) {
1048 +- extracted = accumulate(extracted, transfer);
1049 +- }
1050 +- }
1051 +- // Now that we know the responder is set correctly, we can dispatch
1052 +- // responder type events (directly to the responder).
1053 +- var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
1054 +- isEndish(topLevelType) ? abstractEventTypes.responderRelease :
1055 +- isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
1056 +- if (type) {
1057 +- var data = AbstractEvent.normalizePointerData(nativeEvent);
1058 +- var gesture = AbstractEvent.getPooled(
1059 +- type,
1060 +- responderID,
1061 +- topLevelType,
1062 +- nativeEvent,
1063 +- data
1064 +- );
1065 +- EventPropagators.accumulateDirectDispatches(gesture);
1066 +- extracted = accumulate(extracted, gesture);
1067 +- }
1068 +- if (type === abstractEventTypes.responderRelease) {
1069 +- responderID = null;
1070 ++/**
1071 ++ * @param {string} topLevelType Record from `EventConstants`.
1072 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
1073 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
1074 ++ * @param {object} nativeEvent Native browser event.
1075 ++ * @return {*} An accumulation of `AbstractEvent`s.
1076 ++ * @see {EventPluginHub.extractAbstractEvents}
1077 ++ */
1078 ++var extractAbstractEvents = function(
1079 ++ topLevelType,
1080 ++ topLevelTarget,
1081 ++ topLevelTargetID,
1082 ++ nativeEvent) {
1083 ++ var extracted;
1084 ++ // Must have missed an end event - reset the state here.
1085 ++ if (responderID && isStartish(topLevelType)) {
1086 ++ responderID = null;
1087 ++ }
1088 ++ if (isStartish(topLevelType)) {
1089 ++ isPressing = true;
1090 ++ } else if (isEndish(topLevelType)) {
1091 ++ isPressing = false;
1092 ++ }
1093 ++ if (canTriggerTransfer(topLevelType)) {
1094 ++ var transfer = setResponderAndExtractTransfer(
1095 ++ topLevelType,
1096 ++ topLevelTargetID,
1097 ++ nativeEvent
1098 ++ );
1099 ++ if (transfer) {
1100 ++ extracted = accumulate(extracted, transfer);
1101 + }
1102 +- return extracted;
1103 +- };
1104 ++ }
1105 ++ // Now that we know the responder is set correctly, we can dispatch
1106 ++ // responder type events (directly to the responder).
1107 ++ var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
1108 ++ isEndish(topLevelType) ? abstractEventTypes.responderRelease :
1109 ++ isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
1110 ++ if (type) {
1111 ++ var data = AbstractEvent.normalizePointerData(nativeEvent);
1112 ++ var gesture = AbstractEvent.getPooled(
1113 ++ type,
1114 ++ responderID,
1115 ++ nativeEvent,
1116 ++ data
1117 ++ );
1118 ++ EventPropagators.accumulateDirectDispatches(gesture);
1119 ++ extracted = accumulate(extracted, gesture);
1120 ++ }
1121 ++ if (type === abstractEventTypes.responderRelease) {
1122 ++ responderID = null;
1123 ++ }
1124 ++ return extracted;
1125 ++};
1126 +
1127 + /**
1128 + * Event plugin for formalizing the negotiation between claiming locks on
1129 +diff --git a/static_upstream/react/eventPlugins/SimpleEventPlugin.js b/static_upstream/react/eventPlugins/SimpleEventPlugin.js
1130 +index 8f2cdda..84310e27 100644
1131 +--- a/static_upstream/react/eventPlugins/SimpleEventPlugin.js
1132 ++++ b/static_upstream/react/eventPlugins/SimpleEventPlugin.js
1133 +@@ -141,6 +141,7 @@ var SimpleEventPlugin = {
1134 + /**
1135 + * Same as the default implementation, except cancels the event when return
1136 + * value is false.
1137 ++ *
1138 + * @param {AbstractEvent} AbstractEvent to handle
1139 + * @param {function} Application-level callback
1140 + * @param {string} domID DOM id to pass to the callback.
1141 +@@ -154,48 +155,55 @@ var SimpleEventPlugin = {
1142 + },
1143 +
1144 + /**
1145 +- * @see EventPluginHub.extractAbstractEvents
1146 ++ * @param {string} topLevelType Record from `EventConstants`.
1147 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
1148 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
1149 ++ * @param {object} nativeEvent Native browser event.
1150 ++ * @return {*} An accumulation of `AbstractEvent`s.
1151 ++ * @see {EventPluginHub.extractAbstractEvents}
1152 + */
1153 +- extractAbstractEvents:
1154 +- function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
1155 +- var data;
1156 +- var abstractEventType =
1157 +- SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
1158 +- if (!abstractEventType) {
1159 +- return null;
1160 +- }
1161 +- switch(topLevelType) {
1162 +- case topLevelTypes.topMouseWheel:
1163 +- data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
1164 +- break;
1165 +- case topLevelTypes.topScroll:
1166 +- data = AbstractEvent.normalizeScrollDataFromTarget(renderedTarget);
1167 +- break;
1168 +- case topLevelTypes.topClick:
1169 +- case topLevelTypes.topDoubleClick:
1170 +- case topLevelTypes.topChange:
1171 +- case topLevelTypes.topDOMCharacterDataModified:
1172 +- case topLevelTypes.topMouseDown:
1173 +- case topLevelTypes.topMouseUp:
1174 +- case topLevelTypes.topMouseMove:
1175 +- case topLevelTypes.topTouchMove:
1176 +- case topLevelTypes.topTouchStart:
1177 +- case topLevelTypes.topTouchEnd:
1178 +- data = AbstractEvent.normalizePointerData(nativeEvent);
1179 +- break;
1180 +- default:
1181 +- data = null;
1182 +- }
1183 +- var abstractEvent = AbstractEvent.getPooled(
1184 +- abstractEventType,
1185 +- renderedTargetID,
1186 +- topLevelType,
1187 +- nativeEvent,
1188 +- data
1189 +- );
1190 +- EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
1191 +- return abstractEvent;
1192 ++ extractAbstractEvents: function(
1193 ++ topLevelType,
1194 ++ topLevelTarget,
1195 ++ topLevelTargetID,
1196 ++ nativeEvent) {
1197 ++ var data;
1198 ++ var abstractEventType =
1199 ++ SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
1200 ++ if (!abstractEventType) {
1201 ++ return null;
1202 + }
1203 ++ switch(topLevelType) {
1204 ++ case topLevelTypes.topMouseWheel:
1205 ++ data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
1206 ++ break;
1207 ++ case topLevelTypes.topScroll:
1208 ++ data = AbstractEvent.normalizeScrollDataFromTarget(topLevelTarget);
1209 ++ break;
1210 ++ case topLevelTypes.topClick:
1211 ++ case topLevelTypes.topDoubleClick:
1212 ++ case topLevelTypes.topChange:
1213 ++ case topLevelTypes.topDOMCharacterDataModified:
1214 ++ case topLevelTypes.topMouseDown:
1215 ++ case topLevelTypes.topMouseUp:
1216 ++ case topLevelTypes.topMouseMove:
1217 ++ case topLevelTypes.topTouchMove:
1218 ++ case topLevelTypes.topTouchStart:
1219 ++ case topLevelTypes.topTouchEnd:
1220 ++ data = AbstractEvent.normalizePointerData(nativeEvent);
1221 ++ break;
1222 ++ default:
1223 ++ data = null;
1224 ++ }
1225 ++ var abstractEvent = AbstractEvent.getPooled(
1226 ++ abstractEventType,
1227 ++ topLevelTargetID,
1228 ++ nativeEvent,
1229 ++ data
1230 ++ );
1231 ++ EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
1232 ++ return abstractEvent;
1233 ++ }
1234 + };
1235 +
1236 + SimpleEventPlugin.topLevelTypesToAbstract = {
1237 +diff --git a/static_upstream/react/eventPlugins/TapEventPlugin.js b/static_upstream/react/eventPlugins/TapEventPlugin.js
1238 +index 29e03cf..92c8392 100644
1239 +--- a/static_upstream/react/eventPlugins/TapEventPlugin.js
1240 ++++ b/static_upstream/react/eventPlugins/TapEventPlugin.js
1241 +@@ -1,5 +1,6 @@
1242 + /**
1243 + * @providesModule TapEventPlugin
1244 ++ * @typechecks
1245 + */
1246 +
1247 + "use strict";
1248 +@@ -32,26 +33,27 @@ var abstractEventTypes = {
1249 + };
1250 +
1251 + /**
1252 +- * @see EventPluginHub.extractAbstractEvents
1253 ++ * @param {string} topLevelType Record from `EventConstants`.
1254 ++ * @param {DOMEventTarget} topLevelTarget The listening component root node.
1255 ++ * @param {string} topLevelTargetID ID of `topLevelTarget`.
1256 ++ * @param {object} nativeEvent Native browser event.
1257 ++ * @return {*} An accumulation of `AbstractEvent`s.
1258 ++ * @see {EventPluginHub.extractAbstractEvents}
1259 + */
1260 + var extractAbstractEvents = function(
1261 + topLevelType,
1262 +- nativeEvent,
1263 +- renderedTargetID,
1264 +- renderedTarget) {
1265 +-
1266 ++ topLevelTarget,
1267 ++ topLevelTargetID,
1268 ++ nativeEvent) {
1269 + if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
1270 + return;
1271 + }
1272 + var abstractEvent;
1273 + var dist = eventDistance(startCoords, nativeEvent);
1274 + if (isEndish(topLevelType) && dist < tapMoveThreshold) {
1275 +- var type = abstractEventTypes.touchTap;
1276 +- var abstractTargetID = renderedTargetID;
1277 + abstractEvent = AbstractEvent.getPooled(
1278 +- type,
1279 +- abstractTargetID,
1280 +- topLevelType,
1281 ++ abstractEventTypes.touchTap,
1282 ++ topLevelTargetID,
1283 + nativeEvent
1284 + );
1285 + }
1286 +diff --git a/static_upstream/react/eventPlugins/__tests__/ResponderEventPlugin-test.js b/static_upstream/react/eventPlugins/__tests__/ResponderEventPlugin-test.js
1287 +index c148a5a..1dfb44e 100644
1288 +--- a/static_upstream/react/eventPlugins/__tests__/ResponderEventPlugin-test.js
1289 ++++ b/static_upstream/react/eventPlugins/__tests__/ResponderEventPlugin-test.js
1290 +@@ -153,8 +153,9 @@ var existsInExtraction = function(extracted, test) {
1291 + function assertGrantEvent(id, extracted) {
1292 + var test = function(abstractEvent) {
1293 + return abstractEvent instanceof AbstractEvent &&
1294 +- abstractEvent.type === responderAbstractEventTypes.responderGrant &&
1295 +- abstractEvent.abstractTargetID === id;
1296 ++ abstractEvent.reactEventType ===
1297 ++ responderAbstractEventTypes.responderGrant &&
1298 ++ abstractEvent.reactTargetID === id;
1299 + };
1300 + expect(ResponderEventPlugin.getResponderID()).toBe(id);
1301 + expect(existsInExtraction(extracted, test)).toBe(true);
1302 +@@ -163,8 +164,9 @@ function assertGrantEvent(id, extracted) {
1303 + function assertResponderMoveEvent(id, extracted) {
1304 + var test = function(abstractEvent) {
1305 + return abstractEvent instanceof AbstractEvent &&
1306 +- abstractEvent.type === responderAbstractEventTypes.responderMove &&
1307 +- abstractEvent.abstractTargetID === id;
1308 ++ abstractEvent.reactEventType ===
1309 ++ responderAbstractEventTypes.responderMove &&
1310 ++ abstractEvent.reactTargetID === id;
1311 + };
1312 + expect(ResponderEventPlugin.getResponderID()).toBe(id);
1313 + expect(existsInExtraction(extracted, test)).toBe(true);
1314 +@@ -173,8 +175,9 @@ function assertResponderMoveEvent(id, extracted) {
1315 + function assertTerminateEvent(id, extracted) {
1316 + var test = function(abstractEvent) {
1317 + return abstractEvent instanceof AbstractEvent &&
1318 +- abstractEvent.type === responderAbstractEventTypes.responderTerminate &&
1319 +- abstractEvent.abstractTargetID === id;
1320 ++ abstractEvent.reactEventType ===
1321 ++ responderAbstractEventTypes.responderTerminate &&
1322 ++ abstractEvent.reactTargetID === id;
1323 + };
1324 + expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
1325 + expect(existsInExtraction(extracted, test)).toBe(true);
1326 +@@ -183,8 +186,9 @@ function assertTerminateEvent(id, extracted) {
1327 + function assertRelease(id, extracted) {
1328 + var test = function(abstractEvent) {
1329 + return abstractEvent instanceof AbstractEvent &&
1330 +- abstractEvent.type === responderAbstractEventTypes.responderRelease &&
1331 +- abstractEvent.abstractTargetID === id;
1332 ++ abstractEvent.reactEventType ===
1333 ++ responderAbstractEventTypes.responderRelease &&
1334 ++ abstractEvent.reactTargetID === id;
1335 + };
1336 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
1337 + expect(existsInExtraction(extracted, test)).toBe(true);
src/dom/isEventSupported.js
+13 -1
@@ -20,9 +20,14 @@
20
21 var ExecutionEnvironment = require('ExecutionEnvironment');
22
23 -var testNode;
23 +var testNode, useHasFeature;
24 if (ExecutionEnvironment.canUseDOM) {
25 testNode = document.createElement('div');
26 + useHasFeature =
27 + document.implementation &&
28 + document.implementation.hasFeature &&
29 + // `hasFeature` always returns true in Firefox 19+.
30 + document.implementation.hasFeature('', '') !== true;
31 }
32
33 /**
@@ -44,6 +49,7 @@ function isEventSupported(eventNameSuffix, capture) {
49 return false;
50 }
51 var element = document.createElement('div');
52 +
53 var eventName = 'on' + eventNameSuffix;
54 var isSupported = eventName in element;
55
@@ -55,6 +61,12 @@ function isEventSupported(eventNameSuffix, capture) {
61 }
62 element.removeAttribute(eventName);
63 }
64 +
65 + if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
66 + // This is the only way to test support for the `wheel` event in IE9+.
67 + isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
68 + }
69 +
70 element = null;
71 return isSupported;
72 }
src/event/EventConstants.js
+1 -1
@@ -41,7 +41,7 @@ var topLevelTypes = keyMirror({
41 topMouseOut: null,
42 topMouseOver: null,
43 topMouseUp: null,
44 - topMouseWheel: null,
44 + topWheel: null,
45 topScroll: null,
46 topSubmit: null,
47 topTouchCancel: null,
src/eventPlugins/AnalyticsEventPluginFactory.js
+2 -2
@@ -99,7 +99,7 @@ var analyticsData = {};
99 var topLevelTypesToAnalyticsEvent = {
100 topClick: 'click',
101 topDoubleClick: 'doubleClick',
102 - topMouseWheel: 'mouseWheel',
102 + wheel: 'wheel',
103 topTouchStart: 'touchStart',
104 topTouchEnd: 'touchEnd',
105 topTouchMove: 'touchMove',
@@ -117,7 +117,7 @@ if (__DEV__) {
117 var analyticsEventNameToTopLevelType = {
118 'click': topLevelTypes.topClick,
119 'doubleClick': topLevelTypes.topDoubleClick,
120 - 'mouseWheel': topLevelTypes.topMouseWheel,
120 + 'wheel': topLevelTypes.wheel,
121 'touchStart': topLevelTypes.topTouchStart,
122 'touchEnd': topLevelTypes.topTouchEnd,
123 'touchMove': topLevelTypes.topTouchMove,
src/eventPlugins/SimpleEventPlugin.js
+5 -5
@@ -60,10 +60,10 @@ var SimpleEventPlugin = {
60 captured: keyOf({onClickCapture: true})
61 }
62 },
63 - mouseWheel: {
63 + wheel: {
64 phasedRegistrationNames: {
65 - bubbled: keyOf({onMouseWheel: true}),
66 - captured: keyOf({onMouseWheelCapture: true})
65 + bubbled: keyOf({onWheel: true}),
66 + captured: keyOf({onWheelCapture: true})
67 }
68 },
69 touchStart: {
@@ -188,7 +188,7 @@ var SimpleEventPlugin = {
188 return null;
189 }
190 switch(topLevelType) {
191 - case topLevelTypes.topMouseWheel:
191 + case topLevelTypes.topWheel:
192 data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
193 break;
194 case topLevelTypes.topScroll:
@@ -226,7 +226,7 @@ SimpleEventPlugin.topLevelTypesToAbstract = {
226 topMouseMove: SimpleEventPlugin.abstractEventTypes.mouseMove,
227 topClick: SimpleEventPlugin.abstractEventTypes.click,
228 topDoubleClick: SimpleEventPlugin.abstractEventTypes.doubleClick,
229 - topMouseWheel: SimpleEventPlugin.abstractEventTypes.mouseWheel,
229 + topWheel: SimpleEventPlugin.abstractEventTypes.wheel,
230 topTouchStart: SimpleEventPlugin.abstractEventTypes.touchStart,
231 topTouchEnd: SimpleEventPlugin.abstractEventTypes.touchEnd,
232 topTouchMove: SimpleEventPlugin.abstractEventTypes.touchMove,