Skip to content

Commit

Permalink
Supporting mounting into iframes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
petehunt authored and zpao committed Sep 9, 2013
1 parent cd7d863 commit 647731e
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 17 deletions.
15 changes: 10 additions & 5 deletions src/core/ReactEventEmitter.js
Expand Up @@ -152,8 +152,9 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
* reason, and only in some cases).
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {DOMDocument} contentDocument DOM document to listen on
*/
ensureListening: function(touchNotMouse) {
ensureListening: function(touchNotMouse, contentDocument) {
invariant(
ExecutionEnvironment.canUseDOM,
'ensureListening(...): Cannot toggle event listening in a Worker ' +
Expand All @@ -168,7 +169,10 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
// Call out to base implementation.
ReactEventEmitterMixin.ensureListening.call(
ReactEventEmitter,
touchNotMouse
{
touchNotMouse: touchNotMouse,
contentDocument: contentDocument
}
);
},

Expand Down Expand Up @@ -217,16 +221,17 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
* they bubble to document.
*
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
* @param {DOMDocument} contentDocument Document which owns the container
* @private
* @see http://www.quirksmode.org/dom/events/keys.html.
*/
listenAtTopLevel: function(touchNotMouse) {
listenAtTopLevel: function(touchNotMouse, contentDocument) {
invariant(
!this._isListening,
!contentDocument._isListening,
'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
);
var topLevelTypes = EventConstants.topLevelTypes;
var mountAt = document;
var mountAt = contentDocument;

registerScrollValueMonitoring();
trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
Expand Down
6 changes: 3 additions & 3 deletions src/core/ReactEventEmitterMixin.js
Expand Up @@ -54,9 +54,9 @@ var ReactEventEmitterMixin = {
* @param {*} config Configuration passed through to `listenAtTopLevel`.
*/
ensureListening: function(config) {
if (!this._isListening) {
this.listenAtTopLevel(config);
this._isListening = true;
if (!config.contentDocument._reactIsListening) {
this.listenAtTopLevel(config.touchNotMouse, config.contentDocument);
config.contentDocument._reactIsListening = true;
}
},

Expand Down
14 changes: 11 additions & 3 deletions src/core/ReactMount.js
Expand Up @@ -229,11 +229,19 @@ var ReactMount = {
/**
* Ensures that the top-level event delegation listener is set up. This will
* be invoked some time before the first time any React component is rendered.
* @param {DOMElement} container container we're rendering into
*
* @private
*/
prepareEnvironmentForDOM: function() {
ReactEventEmitter.ensureListening(ReactMount.useTouchEvents);
prepareEnvironmentForDOM: function(container) {
invariant(
container && container.nodeType === 1,

This comment has been minimized.

Copy link
@kassens

kassens Sep 10, 2013

Member

This invariant broke react-page which calls this with document (nodeType === 9)

'prepareEnvironmentForDOM(...): Target container is not a DOM element.'
);
ReactEventEmitter.ensureListening(
ReactMount.useTouchEvents,
container.ownerDocument
);
},

/**
Expand Down Expand Up @@ -269,7 +277,7 @@ var ReactMount = {
* @return {string} reactRoot ID prefix
*/
_registerComponent: function(nextComponent, container) {
ReactMount.prepareEnvironmentForDOM();
ReactMount.prepareEnvironmentForDOM(container);

var reactRootID = ReactMount.registerContainer(container);
instancesByReactRootID[reactRootID] = nextComponent;
Expand Down
8 changes: 4 additions & 4 deletions src/core/__tests__/ReactComponent-test.js
Expand Up @@ -37,15 +37,15 @@ describe('ReactComponent', function() {
expect(function() {
React.renderComponent(<div></div>, [container]);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not valid.'
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
'is not a DOM element.'
);

expect(function() {
React.renderComponent(<div></div>, null);
}).toThrow(
'Invariant Violation: mountComponentIntoNode(...): Target container is ' +
'not valid.'
'Invariant Violation: prepareEnvironmentForDOM(...): Target container ' +
'is not a DOM element.'
);
});

Expand Down
2 changes: 1 addition & 1 deletion src/core/__tests__/ReactEventEmitter-test.js
Expand Up @@ -95,7 +95,7 @@ describe('ReactEventEmitter', function() {
ReactTestUtils = require('ReactTestUtils');
idCallOrder = [];
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
ReactEventEmitter.ensureListening(false);
ReactEventEmitter.ensureListening(false, document);
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin
});
Expand Down
2 changes: 1 addition & 1 deletion src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js
Expand Up @@ -61,7 +61,7 @@ describe('AnalyticsEventPlugin', function() {
'ChangeEventPlugin': ChangeEventPlugin
});

ReactEventEmitter.ensureListening(false);
ReactEventEmitter.ensureListening(false, document);
});

it('should count events correctly', function() {
Expand Down

0 comments on commit 647731e

Please sign in to comment.