Supporting mounting into iframes
Sencha says that separating big components into their own iframes was important for performance: http://www.sencha.com/blog/the-making-of-fastbook-an-html5-love-story. Today the only thing stopping us is that events don't bubble to our events system from an iframe. This diff looks at the owning document of the container and adds top-level listeners to it. It should not change existing behavior and should improve our support for this.
Pete Hunt committed
Sep 9, 2013 at 14:55 UTC
647731e399fc824c02fdf851d91ce4477958e7fe
6 files changed
+30
-17
src/core/ReactEventEmitter.js
+10
-5
@@ -152,8 +152,9 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
152
* reason, and only in some cases).
153
*
154
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
155
+ * @param {DOMDocument} contentDocument DOM document to listen on
156
*/
156
- ensureListening: function(touchNotMouse) {
157
+ ensureListening: function(touchNotMouse, contentDocument) {
158
invariant(
159
ExecutionEnvironment.canUseDOM,
160
'ensureListening(...): Cannot toggle event listening in a Worker ' +
@@ -168,7 +169,10 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
169
// Call out to base implementation.
170
ReactEventEmitterMixin.ensureListening.call(
171
ReactEventEmitter,
171
- touchNotMouse
172
+ {
173
+ touchNotMouse: touchNotMouse,
174
+ contentDocument: contentDocument
175
+ }
176
);
177
},
178
@@ -217,16 +221,17 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
221
* they bubble to document.
222
*
223
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
224
+ * @param {DOMDocument} contentDocument Document which owns the container
225
* @private
226
* @see http://www.quirksmode.org/dom/events/keys.html.
227
*/
223
- listenAtTopLevel: function(touchNotMouse) {
228
+ listenAtTopLevel: function(touchNotMouse, contentDocument) {
229
invariant(
225
- !this._isListening,
230
+ !contentDocument._isListening,
231
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
232
);
233
var topLevelTypes = EventConstants.topLevelTypes;
229
- var mountAt = document;
234
+ var mountAt = contentDocument;
235
236
registerScrollValueMonitoring();
237
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
src/core/ReactEventEmitterMixin.js
+3
-3
@@ -54,9 +54,9 @@ var ReactEventEmitterMixin = {
54
* @param {*} config Configuration passed through to `listenAtTopLevel`.
55
*/
56
ensureListening: function(config) {
57
- if (!this._isListening) {
58
- this.listenAtTopLevel(config);
59
- this._isListening = true;
57
+ if (!config.contentDocument._reactIsListening) {
58
+ this.listenAtTopLevel(config.touchNotMouse, config.contentDocument);
59
+ config.contentDocument._reactIsListening = true;
60
}
61
},
62
src/core/ReactMount.js
+11
-3
@@ -229,11 +229,19 @@ var ReactMount = {
229
/**
230
* Ensures that the top-level event delegation listener is set up. This will
231
* be invoked some time before the first time any React component is rendered.
232
+ * @param {DOMElement} container container we're rendering into
233
*
234
* @private
235
*/
235
- prepareEnvironmentForDOM: function() {
236
- ReactEventEmitter.ensureListening(ReactMount.useTouchEvents);
236
+ prepareEnvironmentForDOM: function(container) {
237
+ invariant(
238
+ container && container.nodeType === 1,
239
+ 'prepareEnvironmentForDOM(...): Target container is not a DOM element.'
240
+ );
241
+ ReactEventEmitter.ensureListening(
242
+ ReactMount.useTouchEvents,
243
+ container.ownerDocument
244
+ );
245
},
246
247
/**
@@ -269,7 +277,7 @@ var ReactMount = {
277
* @return {string} reactRoot ID prefix
278
*/
279
_registerComponent: function(nextComponent, container) {
272
- ReactMount.prepareEnvironmentForDOM();
280
+ ReactMount.prepareEnvironmentForDOM(container);
281
282
var reactRootID = ReactMount.registerContainer(container);
283
instancesByReactRootID[reactRootID] = nextComponent;
src/core/__tests__/ReactComponent-test.js
+4
-4
@@ -37,15 +37,15 @@ describe('ReactComponent', function() {
37
expect(function() {
38
React.renderComponent(<div></div>, [container]);
39
}).toThrow(
40
- 'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
41
- 'not valid.'
40
+ 'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
41
+ 'is not a DOM element.'
42
);
43
44
expect(function() {
45
React.renderComponent(<div></div>, null);
46
}).toThrow(
47
- 'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
48
- 'not valid.'
47
+ 'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
48
+ 'is not a DOM element.'
49
);
50
});
51
src/core/__tests__/ReactEventEmitter-test.js
+1
-1
@@ -95,7 +95,7 @@ describe('ReactEventEmitter', function() {
95
ReactTestUtils = require('ReactTestUtils');
96
idCallOrder = [];
97
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
98
- ReactEventEmitter.ensureListening(false);
98
+ ReactEventEmitter.ensureListening(false, document);
99
EventPluginHub.injection.injectEventPluginsByName({
100
TapEventPlugin: TapEventPlugin
101
});
src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js
+1
-1
@@ -61,7 +61,7 @@ describe('AnalyticsEventPlugin', function() {
61
'ChangeEventPlugin': ChangeEventPlugin
62
});
63
64
- ReactEventEmitter.ensureListening(false);
64
+ ReactEventEmitter.ensureListening(false, document);
65
});
66
67
it('should count events correctly', function() {