Pool ancestors list so event system is reentrant
Ben Alpert committed
Feb 17, 2014 at 15:07 UTC
d8fd1af4a0102d5ace78300cd1b5648c45ea6d66
1 file changed
+52
-31
src/browser/ReactEventTopLevelCallback.js
+52
-31
@@ -19,11 +19,13 @@
19
20
"use strict";
21
22
+var PooledClass = require('PooledClass');
23
var ReactEventEmitter = require('ReactEventEmitter');
24
var ReactInstanceHandles = require('ReactInstanceHandles');
25
var ReactMount = require('ReactMount');
26
27
var getEventTarget = require('getEventTarget');
28
+var mixInto = require('mixInto');
29
30
/**
31
* @type {boolean}
@@ -49,8 +51,52 @@ function findParent(node) {
51
return parent;
52
}
53
54
+/**
55
+ * Calls ReactEventEmitter.handleTopLevel for each node stored in bookKeeping's
56
+ * ancestor list. Separated from createTopLevelCallback to avoid try/finally
57
+ * deoptimization.
58
+ *
59
+ * @param {string} topLevelType
60
+ * @param {DOMEvent} nativeEvent
61
+ * @param {TopLevelCallbackBookKeeping} bookKeeping
62
+ */
63
+function handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping) {
64
+ var topLevelTarget = ReactMount.getFirstReactDOM(
65
+ getEventTarget(nativeEvent)
66
+ ) || window;
67
+
68
+ // Loop through the hierarchy, in case there's any nested components.
69
+ // It's important that we build the array of ancestors before calling any
70
+ // event handlers, because event handlers can modify the DOM, leading to
71
+ // inconsistencies with ReactMount's node cache. See #1105.
72
+ var ancestor = topLevelTarget;
73
+ while (ancestor) {
74
+ bookKeeping.ancestors.push(ancestor);
75
+ ancestor = findParent(ancestor);
76
+ }
77
+
78
+ for (var i = 0, l = bookKeeping.ancestors.length; i < l; i++) {
79
+ topLevelTarget = bookKeeping.ancestors[i];
80
+ var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
81
+ ReactEventEmitter.handleTopLevel(
82
+ topLevelType,
83
+ topLevelTarget,
84
+ topLevelTargetID,
85
+ nativeEvent
86
+ );
87
+ }
88
+}
89
+
90
// Used to store ancestor hierarchy in top level callback
53
-var topLevelCallbackReusableArray = [];
91
+function TopLevelCallbackBookKeeping() {
92
+ this.ancestors = [];
93
+}
94
+mixInto(TopLevelCallbackBookKeeping, {
95
+ destructor: function() {
96
+ this.ancestors.length = 0;
97
+ }
98
+});
99
+PooledClass.addPoolingTo(TopLevelCallbackBookKeeping);
100
101
/**
102
* Top-level callback creator used to implement event handling using delegation.
@@ -88,38 +134,13 @@ var ReactEventTopLevelCallback = {
134
if (!_topLevelListenersEnabled) {
135
return;
136
}
91
- var topLevelTarget = ReactMount.getFirstReactDOM(
92
- getEventTarget(nativeEvent)
93
- ) || window;
94
-
95
- var ancestors = topLevelCallbackReusableArray;
96
- ancestors.length = 0;
97
-
98
- // Loop through the hierarchy, in case there's any nested components.
99
- // It's important that we build the array of ancestors before calling any
100
- // event handlers, because event handlers can modify the DOM, leading to
101
- // inconsistencies with ReactMount's node cache. See #1105.
102
- var ancestor = topLevelTarget;
103
- while (ancestor) {
104
- ancestors.push(ancestor);
105
- ancestor = findParent(ancestor);
106
- }
137
108
- for (var i = 0, l = ancestors.length; i < l; i++) {
109
- topLevelTarget = ancestors[i];
110
- var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
111
- ReactEventEmitter.handleTopLevel(
112
- topLevelType,
113
- topLevelTarget,
114
- topLevelTargetID,
115
- nativeEvent
116
- );
138
+ var bookKeeping = TopLevelCallbackBookKeeping.getPooled();
139
+ try {
140
+ handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping);
141
+ } finally {
142
+ TopLevelCallbackBookKeeping.release(bookKeeping);
143
}
118
-
119
- // Emptying ancestors/topLevelCallbackReusableArray is
120
- // not necessary for correctness, but it helps the GC reclaim
121
- // any nodes that were left at the end of the search.
122
- ancestors.length = 0;
144
};
145
}
146