@@ -25,8 +25,10 @@ var EventPluginRegistry = require('EventPluginRegistry');
2525var ReactEventEmitterMixin = require ( 'ReactEventEmitterMixin' ) ;
2626var ViewportMetrics = require ( 'ViewportMetrics' ) ;
2727
28+ var invariant = require ( 'invariant' ) ;
2829var isEventSupported = require ( 'isEventSupported' ) ;
2930var merge = require ( 'merge' ) ;
31+ var warning = require ( 'warning' ) ;
3032
3133/**
3234 * Summary of `ReactBrowserEventEmitter` event handling:
@@ -83,9 +85,7 @@ var merge = require('merge');
8385 * React Core . General Purpose Event Plugin System
8486 */
8587
86- var alreadyListeningTo = { } ;
8788var isMonitoringScrollValue = false ;
88- var reactTopListenersCounter = 0 ;
8989
9090// For events like 'submit' which don't consistently bubble (which we trap at a
9191// lower node than `document`), binding at `document` would cause duplicate
@@ -130,19 +130,28 @@ var topEventMapping = {
130130 topWheel : 'wheel'
131131} ;
132132
133- /**
134- * To ensure no conflicts with other potential React instances on the page
135- */
136- var topListenersIDKey = "_reactListenersID" + String ( Math . random ( ) ) . slice ( 2 ) ;
133+ // TODO: (chenglou) Alternatively, we could use an internal
134+ // map<IDOfRootNodeInsideContainer, map<eventRegistrationName, eventPlugin>>
135+ var eventsKey = '_reactEvents' ;
137136
138- function getListeningForDocument ( mountAt ) {
137+ function getListenedEvents ( mountAt ) {
139138 // In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
140139 // directly.
141- if ( ! Object . prototype . hasOwnProperty . call ( mountAt , topListenersIDKey ) ) {
142- mountAt [ topListenersIDKey ] = reactTopListenersCounter ++ ;
143- alreadyListeningTo [ mountAt [ topListenersIDKey ] ] = { } ;
140+ if ( ! Object . prototype . hasOwnProperty . call ( mountAt , eventsKey ) ) {
141+ mountAt [ eventsKey ] = { } ;
142+ }
143+ return mountAt [ eventsKey ] ;
144+ }
145+
146+ function removeListenedEvents ( mountAt ) {
147+ if ( ! Object . prototype . hasOwnProperty . call ( mountAt , eventsKey ) ) {
148+ warning (
149+ true ,
150+ 'Tried to remove a React root level listener, but it was not found.'
151+ ) ;
152+ return ;
144153 }
145- return alreadyListeningTo [ mountAt [ topListenersIDKey ] ] ;
154+ delete mountAt [ eventsKey ] ;
146155}
147156
148157/**
@@ -196,7 +205,7 @@ var ReactBrowserEventEmitter = merge(ReactEventEmitterMixin, {
196205 } ,
197206
198207 /**
199- * We listen for bubbled touch events on the document object .
208+ * We listen for bubbled touch events on a root container .
200209 *
201210 * Firefox v8.01 (and possibly others) exhibited strange behavior when
202211 * mounting `onmousemove` events at some node that was not the document
@@ -216,36 +225,37 @@ var ReactBrowserEventEmitter = merge(ReactEventEmitterMixin, {
216225 * @param {string } registrationName Name of listener (e.g. `onClick`).
217226 * @param {object } contentDocumentHandle Document which owns the container
218227 */
219- listenTo : function ( registrationName , contentDocumentHandle ) {
220- var mountAt = contentDocumentHandle ;
221- var isListening = getListeningForDocument ( mountAt ) ;
222- var dependencies = EventPluginRegistry .
223- registrationNameDependencies [ registrationName ] ;
228+ listenTo : function ( registrationName , mountAt ) {
229+ var events = getListenedEvents ( mountAt ) ;
230+ var dependencies =
231+ EventPluginRegistry . registrationNameDependencies [ registrationName ] ;
224232
225233 var topLevelTypes = EventConstants . topLevelTypes ;
226234 for ( var i = 0 , l = dependencies . length ; i < l ; i ++ ) {
235+ // `events` is a mapping of dependency -> event. The map does two
236+ // things: store the fact that a dependency has already been registered,
237+ // and store the event for later removal when the node's unmounted.
227238 var dependency = dependencies [ i ] ;
228- if ( ! (
229- isListening . hasOwnProperty ( dependency ) &&
230- isListening [ dependency ]
231- ) ) {
239+ var event ;
240+
241+ if ( ! events . hasOwnProperty ( dependency ) || events [ dependency ] == null ) {
232242 if ( dependency === topLevelTypes . topWheel ) {
233243 if ( isEventSupported ( 'wheel' ) ) {
234- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
244+ event = ReactBrowserEventEmitter . trapBubbledEvent (
235245 topLevelTypes . topWheel ,
236246 'wheel' ,
237247 mountAt
238248 ) ;
239249 } else if ( isEventSupported ( 'mousewheel' ) ) {
240- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
250+ event = ReactBrowserEventEmitter . trapBubbledEvent (
241251 topLevelTypes . topWheel ,
242252 'mousewheel' ,
243253 mountAt
244254 ) ;
245255 } else {
246256 // Firefox needs to capture a different mouse scroll event.
247257 // @see http://www.quirksmode.org/dom/events/tests/scroll.html
248- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
258+ event = ReactBrowserEventEmitter . trapBubbledEvent (
249259 topLevelTypes . topWheel ,
250260 'DOMMouseScroll' ,
251261 mountAt
@@ -254,61 +264,83 @@ var ReactBrowserEventEmitter = merge(ReactEventEmitterMixin, {
254264 } else if ( dependency === topLevelTypes . topScroll ) {
255265
256266 if ( isEventSupported ( 'scroll' , true ) ) {
257- ReactBrowserEventEmitter . ReactEventListener . trapCapturedEvent (
267+ event = ReactBrowserEventEmitter . trapCapturedEvent (
258268 topLevelTypes . topScroll ,
259269 'scroll' ,
260270 mountAt
261271 ) ;
262272 } else {
263- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
273+ event = ReactBrowserEventEmitter . trapBubbledEvent (
264274 topLevelTypes . topScroll ,
265275 'scroll' ,
266- ReactBrowserEventEmitter . ReactEventListener . WINDOW_HANDLE
276+ ReactBrowserEventEmitter . WINDOW_HANDLE
267277 ) ;
268278 }
269279 } else if ( dependency === topLevelTypes . topFocus ||
270280 dependency === topLevelTypes . topBlur ) {
281+ var event2 ;
271282
272283 if ( isEventSupported ( 'focus' , true ) ) {
273- ReactBrowserEventEmitter . ReactEventListener . trapCapturedEvent (
284+ event = ReactBrowserEventEmitter . trapCapturedEvent (
274285 topLevelTypes . topFocus ,
275286 'focus' ,
276287 mountAt
277288 ) ;
278- ReactBrowserEventEmitter . ReactEventListener . trapCapturedEvent (
289+ event2 = ReactBrowserEventEmitter . trapCapturedEvent (
279290 topLevelTypes . topBlur ,
280291 'blur' ,
281292 mountAt
282293 ) ;
283294 } else if ( isEventSupported ( 'focusin' ) ) {
284295 // IE has `focusin` and `focusout` events which bubble.
285296 // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
286- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
297+ event = ReactBrowserEventEmitter . trapBubbledEvent (
287298 topLevelTypes . topFocus ,
288299 'focusin' ,
289300 mountAt
290301 ) ;
291- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
302+ event2 = ReactBrowserEventEmitter . trapBubbledEvent (
292303 topLevelTypes . topBlur ,
293304 'focusout' ,
294305 mountAt
295306 ) ;
296307 }
297308
298309 // to make sure blur and focus event listeners are only attached once
299- isListening [ topLevelTypes . topBlur ] = true ;
300- isListening [ topLevelTypes . topFocus ] = true ;
310+ events [ topLevelTypes . topFocus ] = event ;
311+ events [ topLevelTypes . topBlur ] = event2 ;
301312 } else if ( topEventMapping . hasOwnProperty ( dependency ) ) {
302- ReactBrowserEventEmitter . ReactEventListener . trapBubbledEvent (
313+ event = ReactBrowserEventEmitter . trapBubbledEvent (
303314 dependency ,
304315 topEventMapping [ dependency ] ,
305316 mountAt
306317 ) ;
307318 }
319+ // As mentioned above, events like `submit` don't bubble to document and
320+ // thus are not attached to it. In that case, there's no `event` (and a
321+ // `remove`) to store. We'll put a `true` placeholder here.
322+ events [ dependency ] = event || true ;
323+ }
324+ }
325+ } ,
308326
309- isListening [ dependency ] = true ;
327+ removeListenedEvents : function ( container ) {
328+ var events = getListenedEvents ( container ) ;
329+ if ( ! events ) {
330+ // Might be that no event was (lazily) added in the first place.
331+ return ;
332+ }
333+ for ( var key in events ) {
334+ if ( ! events . hasOwnProperty ( key ) ) {
335+ continue ;
336+ }
337+ if ( events [ key ] . remove ) {
338+ // See `listenTo`. The event might be a `true` placeholder for things
339+ // like `onSubmit`.
340+ events [ key ] . remove ( ) ;
310341 }
311342 }
343+ removeListenedEvents ( container ) ;
312344 } ,
313345
314346 trapBubbledEvent : function ( topLevelType , handlerBaseName , handle ) {
0 commit comments