@samitouri / QOS-React-2 / commits / 647731e399

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() {