diff --git a/Gruntfile.js b/Gruntfile.js
index de406e42ca4e1..cf1fb14bdbfc8 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -75,6 +75,7 @@ module.exports = function(grunt) {
grunt.registerTask('build:transformer', ['jsx:normal', 'browserify:transformer']);
grunt.registerTask('build:min', ['jsx:normal', 'version-check', 'browserify:min']);
grunt.registerTask('build:addons-min', ['jsx:normal', 'browserify:addonsMin']);
+ grunt.registerTask('build:workers', ['jsx:normal', 'browserify:workers']);
grunt.registerTask('build:withCodeCoverageLogging', [
'jsx:normal',
'version-check',
@@ -217,6 +218,7 @@ module.exports = function(grunt) {
'browserify:addons',
'browserify:min',
'browserify:addonsMin',
+ 'browserify:workers',
'npm-react:release',
'npm-react:pack',
'npm-react-tools:pack',
diff --git a/examples/webworker/example.js b/examples/webworker/example.js
new file mode 100644
index 0000000000000..4a9d53c266c91
--- /dev/null
+++ b/examples/webworker/example.js
@@ -0,0 +1,39 @@
+/**
+ * @jsx React.DOM
+ */
+
+if (typeof React === 'undefined') {
+ importScripts('../../build/react-with-workers.js');
+}
+
+React.Worker.run('./example.js', [], function() {
+ var ExampleApplication = React.createClass({
+ getInitialState: function() {
+ return {red: false};
+ },
+ toggle: function() {
+ this.setState({red: !this.state.red});
+ },
+ render: function() {
+ var elapsed = Math.round(this.props.elapsed / 100);
+ var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
+ var message =
+ 'React has been successfully running for ' + seconds + ' seconds.';
+
+ return React.DOM.p({onClick: this.toggle, style: {color: this.state.red ? 'red' : 'blue'}}, message);
+ }
+ });
+
+ var start = new Date().getTime();
+
+ setInterval(function() {
+ try {
+ React.renderComponent(
+ ExampleApplication({elapsed: new Date().getTime() - start}),
+ 'container'
+ );
+ } catch (e) {
+ console.log(e.stack);
+ }
+ }, 50);
+});
diff --git a/examples/webworker/index.html b/examples/webworker/index.html
new file mode 100644
index 0000000000000..680d68d33a905
--- /dev/null
+++ b/examples/webworker/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+ Basic Example with Precompiled JSX
+
+
+
+ Basic Example with Precompiled JSX
+
+
+ If you can see this, React is not running. Try running:
+
+
npm install -g react-tools
+cd examples/basic-jsx-precompile/
+jsx . build/
+
+ Example Details
+ This is written with JSX in a separate file and precompiled to vanilla JS by running:
+ npm install -g react-tools
+cd examples/basic-jsx-precompile/
+jsx . build/
+
+ Learn more about React at
+ facebook.github.io/react .
+
+
+
+
+
diff --git a/grunt/config/browserify.js b/grunt/config/browserify.js
index 61481e4353414..5733e06859d56 100644
--- a/grunt/config/browserify.js
+++ b/grunt/config/browserify.js
@@ -103,6 +103,18 @@ var addonsMin = _.merge({}, addons, {
after: [minify, bannerify]
});
+var workers = {
+ entries: [
+ './build/modules/ReactWithWorkers.js'
+ ],
+ outfile: './build/react-with-workers.js',
+ debug: false,
+ standalone: 'React',
+ transforms: [envify({NODE_ENV: 'development'})],
+ packageName: 'React (web workers)',
+ after: [es3ify.transform, simpleBannerify]
+};
+
var withCodeCoverageLogging = {
entries: [
'./build/modules/React.js'
@@ -122,5 +134,6 @@ module.exports = {
transformer: transformer,
addons: addons,
addonsMin: addonsMin,
+ workers: workers,
withCodeCoverageLogging: withCodeCoverageLogging
};
diff --git a/grunt/config/jsx.js b/grunt/config/jsx.js
index acafefb4af4be..d66230cebf398 100644
--- a/grunt/config/jsx.js
+++ b/grunt/config/jsx.js
@@ -5,7 +5,8 @@ var _ = require('lodash');
var rootIDs = [
"React",
- "ReactWithAddons"
+ "ReactWithAddons",
+ "ReactWithWorkers"
];
diff --git a/src/browser/ReactDOMNodeHandle.js b/src/browser/ReactDOMNodeHandle.js
new file mode 100644
index 0000000000000..5bb2ea6b8206c
--- /dev/null
+++ b/src/browser/ReactDOMNodeHandle.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMNodeHandle
+ */
+
+"use strict";
+
+var ReactDOMNodeHandleTypes = require('ReactDOMNodeHandleTypes');
+
+var ReactDOMNodeHandle = {
+ getKey: function(handle) {
+ return handle.key;
+ },
+
+ getHandleForReactID: function(reactID) {
+ return {
+ key: 'reactid:' + reactID,
+ type: ReactDOMNodeHandleTypes.REACT_ID,
+ reactID: reactID
+ };
+ },
+
+ getHandleForReactIDTopLevel: function(reactID) {
+ return {
+ key: 'toplevel:' + reactID,
+ type: ReactDOMNodeHandleTypes.REACT_ID_TOP_LEVEL,
+ reactID: reactID
+ };
+ },
+
+ getHandleForContainerID: function(id) {
+ return {
+ key: 'containerID:' + id,
+ type: ReactDOMNodeHandleTypes.CONTAINER,
+ id: id
+ };
+ }
+};
+
+module.exports = ReactDOMNodeHandle;
diff --git a/src/browser/ReactDOMNodeHandleMapping.js b/src/browser/ReactDOMNodeHandleMapping.js
new file mode 100644
index 0000000000000..3d95de7bcbc49
--- /dev/null
+++ b/src/browser/ReactDOMNodeHandleMapping.js
@@ -0,0 +1,113 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMNodeHandleMapping
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
+var ReactInstanceHandles = require('ReactInstanceHandles');
+
+/** Mapping from reactRootID to React component instance. */
+var instancesByReactRootID = {};
+
+/** inverse of above */
+var reactRootIDByContainerKey = {};
+
+/**
+ * @param {object} container DOM node handle that may contain a React component.
+ * @return {?string} A "reactRoot" ID, if a React component is rendered.
+ */
+function getReactRootID(containerHandle) {
+ return reactRootIDByContainerKey[
+ ReactDOMNodeHandle.getKey(containerHandle)
+ ];
+}
+
+var ReactDOMNodeHandleMapping = {
+ /** Exposed for debugging purposes **/
+ _instancesByReactRootID: instancesByReactRootID,
+
+ getReactRootID: getReactRootID,
+
+ /**
+ * Register a component into the instance map and starts scroll value
+ * monitoring
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {object} containerHandle container to render into
+ * @param {string} forceReactRootID reactRootID to use (rather than generating one)
+ * @return {string} reactRoot ID prefix
+ */
+ registerComponent: function(nextComponent, containerHandle, forceReactRootID) {
+ var reactRootID = ReactDOMNodeHandleMapping.registerContainer(
+ containerHandle,
+ forceReactRootID
+ );
+ instancesByReactRootID[reactRootID] = nextComponent;
+ return reactRootID;
+ },
+
+ /**
+ * Registers a container node into which React components will be rendered.
+ * This also creates the "reactRoot" ID that will be assigned to the element
+ * rendered within.
+ *
+ * @param {object} containerHandle DOM node handle to register as a container.
+ * @param {string} forceReactRootID reactRootID to use (rather than generating one)
+ * @return {string} The "reactRoot" ID of elements rendered within.
+ */
+ registerContainer: function(containerHandle, forceReactRootID) {
+ var reactRootID = forceReactRootID || getReactRootID(containerHandle);
+ if (reactRootID) {
+ // If one exists, make sure it is a valid "reactRoot" ID.
+ reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);
+ }
+ if (!reactRootID) {
+ // No valid "reactRoot" ID found, create one.
+ reactRootID = ReactInstanceHandles.createReactRootID();
+ }
+ reactRootIDByContainerKey[
+ ReactDOMNodeHandle.getKey(containerHandle)
+ ] = reactRootID;
+ return reactRootID;
+ },
+
+ /**
+ * Unmounts and destroys the React component rendered in the `container`.
+ *
+ * @param {object} containerHandle DOM node handle containing a React component.
+ * @return {?string} reactRootID that was just unmounted or null if no component is there.
+ */
+ unmountComponentAtNode: function(containerHandle) {
+ var reactRootID = getReactRootID(containerHandle);
+ var component = instancesByReactRootID[reactRootID];
+
+ if (!component) {
+ return null;
+ }
+ delete instancesByReactRootID[reactRootID];
+
+ component.unmountComponent();
+ return reactRootID;
+ },
+
+ getInstanceFromContainer: function(containerHandle) {
+ return instancesByReactRootID[getReactRootID(containerHandle)];
+ }
+};
+
+module.exports = ReactDOMNodeHandleMapping;
diff --git a/src/browser/ReactDOMNodeHandleTypes.js b/src/browser/ReactDOMNodeHandleTypes.js
new file mode 100644
index 0000000000000..ce4c888deca15
--- /dev/null
+++ b/src/browser/ReactDOMNodeHandleTypes.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMNodeHandleTypes
+ */
+
+"use strict";
+
+var keyMirror = require('keyMirror');
+
+var ReactDOMNodeHandleTypes = keyMirror({
+ REACT_ID: null,
+ REACT_ID_TOP_LEVEL: null,
+ CONTAINER: null
+});
+
+module.exports = ReactDOMNodeHandleTypes;
diff --git a/src/browser/ReactEventEmitter.js b/src/browser/ReactEventEmitter.js
index 5f1956d7d9db4..1eef2d4177d57 100644
--- a/src/browser/ReactEventEmitter.js
+++ b/src/browser/ReactEventEmitter.js
@@ -20,14 +20,12 @@
"use strict";
var EventConstants = require('EventConstants');
-var EventListener = require('EventListener');
var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ViewportMetrics = require('ViewportMetrics');
-var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var merge = require('merge');
@@ -138,65 +136,31 @@ function getListeningForDocument(mountAt) {
return alreadyListeningTo[mountAt[topListenersIDKey]];
}
-/**
- * Traps top-level events by using event bubbling.
- *
- * @param {string} topLevelType Record from `EventConstants`.
- * @param {string} handlerBaseName Event name (e.g. "click").
- * @param {DOMEventTarget} element Element on which to attach listener.
- * @internal
- */
-function trapBubbledEvent(topLevelType, handlerBaseName, element) {
- EventListener.listen(
- element,
- handlerBaseName,
- ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
- topLevelType
- )
- );
-}
-
-/**
- * Traps a top-level event by using event capturing.
- *
- * @param {string} topLevelType Record from `EventConstants`.
- * @param {string} handlerBaseName Event name (e.g. "click").
- * @param {DOMEventTarget} element Element on which to attach listener.
- * @internal
- */
-function trapCapturedEvent(topLevelType, handlerBaseName, element) {
- EventListener.capture(
- element,
- handlerBaseName,
- ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
- topLevelType
- )
- );
-}
-
/**
* `ReactEventEmitter` is used to attach top-level event listeners. For example:
*
* ReactEventEmitter.putListener('myID', 'onClick', myFunction);
*
- * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
+ * ReactEventEmitter would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
*
* @internal
*/
var ReactEventEmitter = merge(ReactEventEmitterMixin, {
/**
- * React references `ReactEventTopLevelCallback` using this property in order
- * to allow dependency injection.
+ * Injectable event backend
*/
- TopLevelCallbackCreator: null,
+ ReactEventListener: null,
injection: {
/**
* @param {function} TopLevelCallbackCreator
*/
- injectTopLevelCallbackCreator: function(TopLevelCallbackCreator) {
- ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
+ injectReactEventListener: function(ReactEventListener) {
+ ReactEventListener.setHandleTopLevel(
+ ReactEventEmitter.handleTopLevel
+ );
+ ReactEventEmitter.ReactEventListener = ReactEventListener;
}
},
@@ -206,13 +170,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
* @param {boolean} enabled True if callbacks should be enabled.
*/
setEnabled: function(enabled) {
- invariant(
- ExecutionEnvironment.canUseDOM,
- 'setEnabled(...): Cannot toggle event listening in a Worker thread. ' +
- 'This is likely a bug in the framework. Please report immediately.'
- );
- if (ReactEventEmitter.TopLevelCallbackCreator) {
- ReactEventEmitter.TopLevelCallbackCreator.setEnabled(enabled);
+ if (ReactEventEmitter.ReactEventListener) {
+ ReactEventEmitter.ReactEventListener.setEnabled(enabled);
}
},
@@ -221,8 +180,8 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
*/
isEnabled: function() {
return !!(
- ReactEventEmitter.TopLevelCallbackCreator &&
- ReactEventEmitter.TopLevelCallbackCreator.isEnabled()
+ ReactEventEmitter.ReactEventListener &&
+ ReactEventEmitter.ReactEventListener.isEnabled()
);
},
@@ -264,13 +223,13 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
if (topLevelType === topLevelTypes.topWheel) {
if (isEventSupported('wheel')) {
- trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
+ ReactEventEmitter.trapBubbledEvent(topLevelTypes.topWheel, 'wheel', mountAt);
} else if (isEventSupported('mousewheel')) {
- trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
+ ReactEventEmitter.trapBubbledEvent(topLevelTypes.topWheel, 'mousewheel', mountAt);
} else {
// Firefox needs to capture a different mouse scroll event.
// @see http://www.quirksmode.org/dom/events/tests/scroll.html
- trapBubbledEvent(
+ ReactEventEmitter.trapBubbledEvent(
topLevelTypes.topWheel,
'DOMMouseScroll',
mountAt);
@@ -278,28 +237,28 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
} else if (topLevelType === topLevelTypes.topScroll) {
if (isEventSupported('scroll', true)) {
- trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
+ ReactEventEmitter.trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
} else {
- trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
+ ReactEventEmitter.trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
}
} else if (topLevelType === topLevelTypes.topFocus ||
topLevelType === topLevelTypes.topBlur) {
if (isEventSupported('focus', true)) {
- trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
- trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
+ ReactEventEmitter.trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
+ ReactEventEmitter.trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
} else if (isEventSupported('focusin')) {
// IE has `focusin` and `focusout` events which bubble.
// @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
- trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
- trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
+ ReactEventEmitter.trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
+ ReactEventEmitter.trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
}
// to make sure blur and focus event listeners are only attached once
isListening[topLevelTypes.topBlur] = true;
isListening[topLevelTypes.topFocus] = true;
} else if (topEventMapping.hasOwnProperty(dependency)) {
- trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
+ ReactEventEmitter.trapBubbledEvent(topLevelType, topEventMapping[dependency], mountAt);
}
isListening[dependency] = true;
@@ -307,6 +266,22 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
}
},
+ trapBubbledEvent: function(topLevelType, handlerBaseName, element) {
+ ReactEventEmitter.ReactEventListener.trapBubbledEvent(
+ topLevelType,
+ handlerBaseName,
+ element
+ );
+ },
+
+ trapCapturedEvent: function(topLevelType, handlerBaseName, element) {
+ ReactEventEmitter.ReactEventListener.trapCapturedEvent(
+ topLevelType,
+ handlerBaseName,
+ element
+ );
+ },
+
/**
* Listens to window scroll and resize events. We cache scroll values so that
* application code can access them without triggering reflows.
@@ -318,8 +293,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
ensureScrollValueMonitoring: function(){
if (!isMonitoringScrollValue) {
var refresh = ViewportMetrics.refreshScrollValues;
- EventListener.listen(window, 'scroll', refresh);
- EventListener.listen(window, 'resize', refresh);
+ ReactEventEmitter.ReactEventListener.monitorScrollValue(refresh);
isMonitoringScrollValue = true;
}
},
@@ -334,12 +308,7 @@ var ReactEventEmitter = merge(ReactEventEmitterMixin, {
deleteListener: EventPluginHub.deleteListener,
- deleteAllListeners: EventPluginHub.deleteAllListeners,
-
- trapBubbledEvent: trapBubbledEvent,
-
- trapCapturedEvent: trapCapturedEvent
-
+ deleteAllListeners: EventPluginHub.deleteAllListeners
});
module.exports = ReactEventEmitter;
diff --git a/src/browser/ReactPutListenerQueue.js b/src/browser/ReactPutListenerQueue.js
index e81966e261817..230b2891c9379 100644
--- a/src/browser/ReactPutListenerQueue.js
+++ b/src/browser/ReactPutListenerQueue.js
@@ -28,8 +28,9 @@ function ReactPutListenerQueue() {
}
mixInto(ReactPutListenerQueue, {
- enqueuePutListener: function(rootNodeID, propKey, propValue) {
+ enqueuePutListener: function(handle, rootNodeID, propKey, propValue) {
this.listenersToPut.push({
+ handle: handle,
rootNodeID: rootNodeID,
propKey: propKey,
propValue: propValue
@@ -39,6 +40,10 @@ mixInto(ReactPutListenerQueue, {
putListeners: function() {
for (var i = 0; i < this.listenersToPut.length; i++) {
var listenerToPut = this.listenersToPut[i];
+ ReactEventEmitter.listenTo(
+ listenerToPut.propKey,
+ listenerToPut.handle
+ );
ReactEventEmitter.putListener(
listenerToPut.rootNodeID,
listenerToPut.propKey,
diff --git a/src/browser/ReactWithWorkers.js b/src/browser/ReactWithWorkers.js
new file mode 100644
index 0000000000000..6dfb2e302cc69
--- /dev/null
+++ b/src/browser/ReactWithWorkers.js
@@ -0,0 +1,106 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactWithWorkers
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var DOMPropertyOperations = require('DOMPropertyOperations');
+var EventPluginUtils = require('EventPluginUtils');
+var ReactChildren = require('ReactChildren');
+var ReactComponent = require('ReactComponent');
+var ReactCompositeComponent = require('ReactCompositeComponent');
+var ReactContext = require('ReactContext');
+var ReactCurrentOwner = require('ReactCurrentOwner');
+var ReactDefaultInjection = require('ReactDefaultInjection');
+var ReactDescriptor = require('ReactDescriptor');
+var ReactDOM = require('ReactDOM');
+var ReactDOMComponent = require('ReactDOMComponent');
+var ReactWorkerInjection = require('ReactWorkerInjection');
+var ReactInstanceHandles = require('ReactInstanceHandles');
+var ReactMount = require('ReactMount');
+var ReactMultiChild = require('ReactMultiChild');
+var ReactPerf = require('ReactPerf');
+var ReactPropTypes = require('ReactPropTypes');
+var ReactServerRendering = require('ReactServerRendering');
+var ReactTextComponent = require('ReactTextComponent');
+var ReactWorker = require('ReactWorker');
+var ReactWorkerMount = require('ReactWorkerMount');
+
+var onlyChild = require('onlyChild');
+
+if (ExecutionEnvironment.canUseDOM) {
+ ReactDefaultInjection.inject();
+} else {
+ ReactWorkerInjection.inject();
+}
+
+var React = {
+ Children: {
+ map: ReactChildren.map,
+ forEach: ReactChildren.forEach,
+ only: onlyChild
+ },
+ DOM: ReactDOM,
+ PropTypes: ReactPropTypes,
+ initializeTouchEvents: function(shouldUseTouch) {
+ EventPluginUtils.useTouchEvents = shouldUseTouch;
+ },
+ Worker: ReactWorker,
+ createClass: ReactCompositeComponent.createClass,
+ constructAndRenderComponent: ReactMount.constructAndRenderComponent,
+ constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
+ renderComponent: ReactPerf.measure(
+ 'React',
+ 'renderComponent',
+ ReactWorkerMount.renderComponent
+ ),
+ renderComponentToString: ReactServerRendering.renderComponentToString,
+ renderComponentToStaticMarkup:
+ ReactServerRendering.renderComponentToStaticMarkup,
+ unmountComponentAtNode: ReactMount.unmountComponentAtNode,
+ isValidClass: ReactDescriptor.isValidFactory,
+ isValidComponent: ReactDescriptor.isValidDescriptor,
+ withContext: ReactContext.withContext,
+ __internals: {
+ Component: ReactComponent,
+ CurrentOwner: ReactCurrentOwner,
+ DOMComponent: ReactDOMComponent,
+ DOMPropertyOperations: DOMPropertyOperations,
+ InstanceHandles: ReactInstanceHandles,
+ Mount: ExecutionEnvironment.canUseDOM ? ReactMount : ReactWorkerMount,
+ MultiChild: ReactMultiChild,
+ TextComponent: ReactTextComponent
+ }
+};
+
+if (__DEV__) {
+ if (ExecutionEnvironment.canUseDOM &&
+ window.top === window.self &&
+ navigator.userAgent.indexOf('Chrome') > -1) {
+ console.debug(
+ 'Download the React DevTools for a better development experience: ' +
+ 'http://fb.me/react-devtools'
+ );
+ }
+}
+
+// Version exists only in the open-source version of React, not in Facebook's
+// internal version.
+React.version = '0.11.0-alpha';
+
+module.exports = React;
diff --git a/src/browser/RemoteModule.js b/src/browser/RemoteModule.js
new file mode 100644
index 0000000000000..4a3d6ab71df50
--- /dev/null
+++ b/src/browser/RemoteModule.js
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule RemoteModule
+ * @typechecks static-only
+ */
+
+// TODO: use a better bridging system that doesn't marshal strings all
+// the time.
+class RemoteModule {
+ constructor(target, name, methods) {
+ this.target = target;
+ this.name = name;
+ for (var method in methods) {
+ this[method] = this.invoke.bind(this, method);
+ }
+ }
+
+ invoke(name) {
+ // No return values allowed!
+ var args = Array.prototype.slice.call(arguments, 1);
+ this.target.postMessage([this.name, name, args]);
+ }
+}
+
+module.exports = RemoteModule;
diff --git a/src/browser/RemoteModuleServer.js b/src/browser/RemoteModuleServer.js
new file mode 100644
index 0000000000000..83a2af6938857
--- /dev/null
+++ b/src/browser/RemoteModuleServer.js
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule RemoteModuleServer
+ * @typechecks static-only
+ */
+
+var invariant = require('invariant');
+
+class RemoteModuleServer {
+ /**
+ * @param {object} target web worker receiving messages for this server.
+ * @param {object} modules mapping of module name to module instance
+ */
+ constructor(target, modules) {
+ invariant(!target.onmessage, 'target already has an onmessage handler');
+ this.target = target;
+ this.modules = modules;
+
+ this.target.onmessage = this.handleMessage.bind(this);
+ }
+
+ handleMessage(event) {
+ var moduleName = event.data[0];
+ var methodName = event.data[1];
+ var args = event.data[2];
+
+ invariant(
+ this.modules[moduleName],
+ 'Module name %s not found',
+ moduleName
+ );
+ invariant(
+ this.modules[moduleName][methodName].apply,
+ 'Method %s.%s not found',
+ moduleName,
+ methodName
+ );
+
+ try {
+ this.modules[moduleName][methodName].apply(
+ this.modules[moduleName],
+ args
+ );
+ } catch (e) {
+ console.log(e.stack);
+ }
+ }
+
+ destroy() {
+ this.target.onmessage = null;
+ }
+}
+
+module.exports = RemoteModuleServer;
diff --git a/src/browser/__tests__/ReactDOM-test.js b/src/browser/__tests__/ReactDOM-test.js
index da0fadb6e20ee..47984d79bd769 100644
--- a/src/browser/__tests__/ReactDOM-test.js
+++ b/src/browser/__tests__/ReactDOM-test.js
@@ -23,7 +23,7 @@
var React = require('React');
var ReactDOM = require('ReactDOM');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactTestUtils = require('ReactTestUtils');
describe('ReactDOM', function() {
@@ -60,7 +60,7 @@ describe('ReactDOM', function() {
var argDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div(null, 'child')
);
- var argNode = ReactMount.getNode(argDiv._rootNodeID);
+ var argNode = ReactDOMNodeMapping.getNode(argDiv._rootNodeID);
expect(argNode.innerHTML).toBe('child');
});
@@ -68,7 +68,7 @@ describe('ReactDOM', function() {
var conflictDiv = ReactTestUtils.renderIntoDocument(
ReactDOM.div({children: 'fakechild'}, 'child')
);
- var conflictNode = ReactMount.getNode(conflictDiv._rootNodeID);
+ var conflictNode = ReactDOMNodeMapping.getNode(conflictDiv._rootNodeID);
expect(conflictNode.innerHTML).toBe('child');
});
@@ -110,7 +110,7 @@ describe('ReactDOM', function() {
theBird:
}
});
- var root = ReactMount.getNode(myDiv._rootNodeID);
+ var root = ReactDOMNodeMapping.getNode(myDiv._rootNodeID);
var dog = root.childNodes[0];
expect(dog.className).toBe('bigdog');
});
diff --git a/src/browser/__tests__/ReactEventEmitter-test.js b/src/browser/__tests__/ReactEventEmitter-test.js
index 2aeaecbedfb19..5c169a9bb5687 100644
--- a/src/browser/__tests__/ReactEventEmitter-test.js
+++ b/src/browser/__tests__/ReactEventEmitter-test.js
@@ -21,7 +21,7 @@
require('mock-modules')
.dontMock('BrowserScroll')
.dontMock('EventPluginHub')
- .dontMock('ReactMount')
+ .dontMock('ReactDOMNodeMapping')
.dontMock('ReactEventEmitter')
.dontMock('ReactInstanceHandles')
.dontMock('EventPluginHub')
@@ -33,14 +33,14 @@ require('mock-modules')
var keyOf = require('keyOf');
var mocks = require('mocks');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var idToNode = {};
-var getID = ReactMount.getID;
+var getID = ReactDOMNodeMapping.getID;
var setID = function(el, id) {
- ReactMount.setID(el, id);
+ ReactDOMNodeMapping.setID(el, id);
idToNode[id] = el;
};
-var oldGetNode = ReactMount.getNode;
+var oldGetNode = ReactDOMNodeMapping.getNode;
var EventPluginHub;
var ReactEventEmitter;
@@ -82,6 +82,8 @@ setID(CHILD, '.0.0.0.0');
setID(PARENT, '.0.0.0');
setID(GRANDPARENT, '.0.0');
+var renderedHandle;
+
function registerSimpleTestHandler() {
ReactEventEmitter.putListener(getID(CHILD), ON_CLICK_KEY, LISTENER);
var listener = ReactEventEmitter.getListener(getID(CHILD), ON_CLICK_KEY);
@@ -96,11 +98,11 @@ describe('ReactEventEmitter', function() {
LISTENER.mockClear();
EventPluginHub = require('EventPluginHub');
TapEventPlugin = require('TapEventPlugin');
- ReactMount = require('ReactMount');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
EventListener = require('EventListener');
ReactEventEmitter = require('ReactEventEmitter');
ReactTestUtils = require('ReactTestUtils');
- ReactMount.getNode = function(id) {
+ ReactDOMNodeMapping.getNode = function(id) {
return idToNode[id];
};
idCallOrder = [];
@@ -108,10 +110,16 @@ describe('ReactEventEmitter', function() {
EventPluginHub.injection.injectEventPluginsByName({
TapEventPlugin: TapEventPlugin
});
+
+ var ReactDOM = require('ReactDOM');
+ var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
+ renderedHandle = ReactDOMNodeHandle.getHandleForReactIDTopLevel(
+ ReactTestUtils.renderIntoDocument(ReactDOM.div())._rootNodeID
+ );
});
afterEach(function() {
- ReactMount.getNode = oldGetNode;
+ ReactDOMNodeMapping.getNode = oldGetNode;
});
it('should store a listener correctly', function() {
@@ -361,15 +369,15 @@ describe('ReactEventEmitter', function() {
it('should listen to events only once', function() {
spyOn(EventListener, 'listen');
- ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
- ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
+ ReactEventEmitter.listenTo(ON_CLICK_KEY, renderedHandle);
+ ReactEventEmitter.listenTo(ON_CLICK_KEY, renderedHandle);
expect(EventListener.listen.callCount).toBe(1);
});
it('should work with event plugins without dependencies', function() {
spyOn(EventListener, 'listen');
- ReactEventEmitter.listenTo(ON_CLICK_KEY, document);
+ ReactEventEmitter.listenTo(ON_CLICK_KEY, renderedHandle);
expect(EventListener.listen.argsForCall[0][1]).toBe('click');
});
@@ -378,7 +386,7 @@ describe('ReactEventEmitter', function() {
spyOn(EventListener, 'listen');
spyOn(EventListener, 'capture');
- ReactEventEmitter.listenTo(ON_CHANGE_KEY, document);
+ ReactEventEmitter.listenTo(ON_CHANGE_KEY, renderedHandle);
var setEventListeners = [];
var listenCalls = EventListener.listen.argsForCall;
diff --git a/src/browser/eventPlugins/EnterLeaveEventPlugin.js b/src/browser/eventPlugins/EnterLeaveEventPlugin.js
index 6dbfaa4399566..7aaef18f6a82f 100644
--- a/src/browser/eventPlugins/EnterLeaveEventPlugin.js
+++ b/src/browser/eventPlugins/EnterLeaveEventPlugin.js
@@ -23,11 +23,11 @@ var EventConstants = require('EventConstants');
var EventPropagators = require('EventPropagators');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var keyOf = require('keyOf');
var topLevelTypes = EventConstants.topLevelTypes;
-var getFirstReactDOM = ReactMount.getFirstReactDOM;
+var getFirstReactDOM = ReactDOMNodeMapping.getFirstReactDOM;
var eventTypes = {
mouseEnter: {
@@ -111,8 +111,8 @@ var EnterLeaveEventPlugin = {
return null;
}
- var fromID = from ? ReactMount.getID(from) : '';
- var toID = to ? ReactMount.getID(to) : '';
+ var fromID = from ? ReactDOMNodeMapping.getID(from) : '';
+ var toID = to ? ReactDOMNodeMapping.getID(to) : '';
var leave = SyntheticMouseEvent.getPooled(
eventTypes.mouseLeave,
diff --git a/src/browser/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js b/src/browser/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
index dd61e4b1ade71..693a29a9272c0 100644
--- a/src/browser/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
+++ b/src/browser/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
@@ -24,7 +24,7 @@
var EnterLeaveEventPlugin;
var EventConstants;
var React;
-var ReactMount;
+var ReactDOMNodeMapping;
var topLevelTypes;
@@ -35,7 +35,7 @@ describe('EnterLeaveEventPlugin', function() {
EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
EventConstants = require('EventConstants');
React = require('React');
- ReactMount = require('ReactMount');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
topLevelTypes = EventConstants.topLevelTypes;
});
@@ -56,7 +56,7 @@ describe('EnterLeaveEventPlugin', function() {
var extracted = EnterLeaveEventPlugin.extractEvents(
topLevelTypes.topMouseOver,
div,
- ReactMount.getID(div),
+ ReactDOMNodeMapping.getID(div),
{target: div}
);
expect(extracted.length).toBe(2);
diff --git a/src/browser/ui/dom/getEventTarget.js b/src/browser/getEventTarget.js
similarity index 88%
rename from src/browser/ui/dom/getEventTarget.js
rename to src/browser/getEventTarget.js
index 37bc41b50f72d..dd77d3ea1f489 100644
--- a/src/browser/ui/dom/getEventTarget.js
+++ b/src/browser/getEventTarget.js
@@ -19,6 +19,8 @@
"use strict";
+var ExecutionEnvironment = require('ExecutionEnvironment');
+
/**
* Gets the target node from a native browser event by accounting for
* inconsistencies in browser DOM APIs.
@@ -27,7 +29,7 @@
* @return {DOMEventTarget} Target node.
*/
function getEventTarget(nativeEvent) {
- var target = nativeEvent.target || nativeEvent.srcElement || window;
+ var target = nativeEvent.target || nativeEvent.srcElement || ExecutionEnvironment.global;
// Safari may fire events on text nodes (Node.TEXT_NODE is 3).
// @see http://www.quirksmode.org/js/events_properties.html
return target.nodeType === 3 ? target.parentNode : target;
diff --git a/src/browser/server/__tests__/ReactServerRendering-test.js b/src/browser/server/__tests__/ReactServerRendering-test.js
index 895ce51238e81..c75cf1d25f456 100644
--- a/src/browser/server/__tests__/ReactServerRendering-test.js
+++ b/src/browser/server/__tests__/ReactServerRendering-test.js
@@ -24,7 +24,7 @@
require('mock-modules')
.dontMock('ExecutionEnvironment')
.dontMock('React')
- .dontMock('ReactMount')
+ .dontMock('ReactDOMNodeMapping')
.dontMock('ReactServerRendering')
.dontMock('ReactTestUtils')
.dontMock('ReactMarkupChecksum');
@@ -32,7 +32,7 @@ require('mock-modules')
var mocks = require('mocks');
var React;
-var ReactMount;
+var ReactDOMNodeMapping;
var ReactTestUtils;
var ReactServerRendering;
var ReactMarkupChecksum;
@@ -44,7 +44,7 @@ describe('ReactServerRendering', function() {
beforeEach(function() {
require('mock-modules').dumpCache();
React = require('React');
- ReactMount = require('ReactMount');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
ReactTestUtils = require('ReactTestUtils');
ExecutionEnvironment = require('ExecutionEnvironment');
ExecutionEnvironment.canUseDOM = false;
@@ -211,7 +211,6 @@ describe('ReactServerRendering', function() {
);
ExecutionEnvironment.canUseDOM = true;
element.innerHTML = lastMarkup + ' __sentinel__';
-
React.renderComponent( , element);
expect(mountCount).toEqual(3);
expect(element.innerHTML.indexOf('__sentinel__') > -1).toBe(true);
diff --git a/src/browser/syntheticEvents/SyntheticUIEvent.js b/src/browser/syntheticEvents/SyntheticUIEvent.js
index 02537f3af3b08..5657c9b22d40f 100644
--- a/src/browser/syntheticEvents/SyntheticUIEvent.js
+++ b/src/browser/syntheticEvents/SyntheticUIEvent.js
@@ -19,6 +19,7 @@
"use strict";
+var ExecutionEnvironment = require('ExecutionEnvironment');
var SyntheticEvent = require('SyntheticEvent');
var getEventTarget = require('getEventTarget');
@@ -44,7 +45,7 @@ var UIEventInterface = {
if (doc) {
return doc.defaultView || doc.parentWindow;
} else {
- return window;
+ return ExecutionEnvironment.global;
}
},
detail: function(event) {
diff --git a/src/browser/ui/ReactBrowserComponentMixin.js b/src/browser/ui/ReactBrowserComponentMixin.js
index efb81599918ef..b64eb2d4aa177 100644
--- a/src/browser/ui/ReactBrowserComponentMixin.js
+++ b/src/browser/ui/ReactBrowserComponentMixin.js
@@ -19,7 +19,8 @@
"use strict";
var ReactEmptyComponent = require('ReactEmptyComponent');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
+
var invariant = require('invariant');
@@ -39,7 +40,7 @@ var ReactBrowserComponentMixin = {
if (ReactEmptyComponent.isNullComponentID(this._rootNodeID)) {
return null;
}
- return ReactMount.getNode(this._rootNodeID);
+ return ReactDOMNodeMapping.getNode(this._rootNodeID);
}
};
diff --git a/src/browser/ui/ReactComponentBrowserEnvironment.js b/src/browser/ui/ReactComponentBrowserEnvironment.js
index a04bb79067170..e0b2b0abbd110 100644
--- a/src/browser/ui/ReactComponentBrowserEnvironment.js
+++ b/src/browser/ui/ReactComponentBrowserEnvironment.js
@@ -22,15 +22,13 @@
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactPerf = require('ReactPerf');
var ReactReconcileTransaction = require('ReactReconcileTransaction');
-var getReactRootElementInContainer = require('getReactRootElementInContainer');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
-
var ELEMENT_NODE_TYPE = 1;
var DOC_NODE_TYPE = 9;
@@ -52,19 +50,21 @@ var ReactComponentBrowserEnvironment = {
* @private
*/
unmountIDFromEnvironment: function(rootNodeID) {
- ReactMount.purgeID(rootNodeID);
+ ReactDOMNodeMapping.purgeID(rootNodeID);
},
/**
* @param {string} markup Markup string to place into the DOM Element.
- * @param {DOMElement} container DOM Element to insert markup into.
+ * @param {object} handle DOM node handle to insert markup into.
* @param {boolean} shouldReuseMarkup Should reuse the existing markup in the
* container if possible.
*/
mountImageIntoNode: ReactPerf.measure(
'ReactComponentBrowserEnvironment',
'mountImageIntoNode',
- function(markup, container, shouldReuseMarkup) {
+ function(markup, handle, shouldReuseMarkup) {
+ var container = ReactDOMNodeMapping.resolveDOMNodeHandle(handle);
+
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
@@ -76,7 +76,7 @@ var ReactComponentBrowserEnvironment = {
if (shouldReuseMarkup) {
if (ReactMarkupChecksum.canReuseMarkup(
markup,
- getReactRootElementInContainer(container))) {
+ ReactDOMNodeMapping.getReactRootElementInContainer(container))) {
return;
} else {
invariant(
diff --git a/src/browser/ui/ReactDOMComponent.js b/src/browser/ui/ReactDOMComponent.js
index e8ecbbf230276..ca43ae0d19cee 100644
--- a/src/browser/ui/ReactDOMComponent.js
+++ b/src/browser/ui/ReactDOMComponent.js
@@ -24,8 +24,8 @@ var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponent = require('ReactComponent');
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
var ReactEventEmitter = require('ReactEventEmitter');
-var ReactMount = require('ReactMount');
var ReactMultiChild = require('ReactMultiChild');
var ReactPerf = require('ReactPerf');
@@ -36,7 +36,6 @@ var merge = require('merge');
var mixInto = require('mixInto');
var deleteListener = ReactEventEmitter.deleteListener;
-var listenTo = ReactEventEmitter.listenTo;
var registrationNameModules = ReactEventEmitter.registrationNameModules;
// For quickly matching children type, to test if can be treated as content.
@@ -44,8 +43,6 @@ var CONTENT_TYPES = {'string': true, 'number': true};
var STYLE = keyOf({style: null});
-var ELEMENT_NODE_TYPE = 1;
-
/**
* @param {?object} props
*/
@@ -66,14 +63,9 @@ function assertValidProps(props) {
}
function putListener(id, registrationName, listener, transaction) {
- var container = ReactMount.findReactContainerForID(id);
- if (container) {
- var doc = container.nodeType === ELEMENT_NODE_TYPE ?
- container.ownerDocument :
- container;
- listenTo(registrationName, doc);
- }
+ var handle = ReactDOMNodeHandle.getHandleForReactIDTopLevel(id);
transaction.getPutListenerQueue().enqueuePutListener(
+ handle,
id,
registrationName,
listener
@@ -386,7 +378,7 @@ ReactDOMComponent.Mixin = {
}
} else if (nextHtml != null) {
if (lastHtml !== nextHtml) {
- ReactComponent.BackendIDOperations.updateInnerHTMLByID(
+ ReactComponent.BackendIDOperations.updateImageByID(
this._rootNodeID,
nextHtml
);
diff --git a/src/browser/ui/ReactDOMIDOperations.js b/src/browser/ui/ReactDOMIDOperations.js
index ea206cd065b90..f275774ee433b 100644
--- a/src/browser/ui/ReactDOMIDOperations.js
+++ b/src/browser/ui/ReactDOMIDOperations.js
@@ -24,7 +24,7 @@
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMPropertyOperations = require('DOMPropertyOperations');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactPerf = require('ReactPerf');
var invariant = require('invariant');
@@ -38,7 +38,7 @@ var setInnerHTML = require('setInnerHTML');
*/
var INVALID_PROPERTY_ERRORS = {
dangerouslySetInnerHTML:
- '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
+ '`dangerouslySetInnerHTML` must be set using `updateImageByID()`.',
style: '`style` must be set using `updateStylesByID()`.'
};
@@ -61,7 +61,7 @@ var ReactDOMIDOperations = {
'ReactDOMIDOperations',
'updatePropertyByID',
function(id, name, value) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
@@ -91,7 +91,7 @@ var ReactDOMIDOperations = {
'ReactDOMIDOperations',
'deletePropertyByID',
function(id, name, value) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
invariant(
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
'updatePropertyByID(...): %s',
@@ -113,7 +113,7 @@ var ReactDOMIDOperations = {
'ReactDOMIDOperations',
'updateStylesByID',
function(id, styles) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
CSSPropertyOperations.setValueForStyles(node, styles);
}
),
@@ -125,11 +125,11 @@ var ReactDOMIDOperations = {
* @param {string} html An HTML string.
* @internal
*/
- updateInnerHTMLByID: ReactPerf.measure(
+ updateImageByID: ReactPerf.measure(
'ReactDOMIDOperations',
- 'updateInnerHTMLByID',
+ 'updateImageByID',
function(id, html) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
setInnerHTML(node, html);
}
),
@@ -145,7 +145,7 @@ var ReactDOMIDOperations = {
'ReactDOMIDOperations',
'updateTextContentByID',
function(id, content) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
DOMChildrenOperations.updateTextContent(node, content);
}
),
@@ -162,7 +162,7 @@ var ReactDOMIDOperations = {
'ReactDOMIDOperations',
'dangerouslyReplaceNodeWithMarkupByID',
function(id, markup) {
- var node = ReactMount.getNode(id);
+ var node = ReactDOMNodeMapping.getNode(id);
DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
}
),
@@ -179,7 +179,7 @@ var ReactDOMIDOperations = {
'dangerouslyProcessChildrenUpdates',
function(updates, markup) {
for (var i = 0; i < updates.length; i++) {
- updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
+ updates[i].parentNode = ReactDOMNodeMapping.getNode(updates[i].parentID);
}
DOMChildrenOperations.processUpdates(updates, markup);
}
diff --git a/src/browser/ui/ReactDOMNodeMapping.js b/src/browser/ui/ReactDOMNodeMapping.js
new file mode 100644
index 0000000000000..168f48e4c605c
--- /dev/null
+++ b/src/browser/ui/ReactDOMNodeMapping.js
@@ -0,0 +1,566 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMNodeMapping
+ */
+
+"use strict";
+
+var DOMProperty = require('DOMProperty');
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
+
+var ReactDOMNodeHandleMapping = require('ReactDOMNodeHandleMapping');
+var ReactDOMNodeHandleTypes = require('ReactDOMNodeHandleTypes');
+var ReactEventEmitter = require('ReactEventEmitter');
+var ReactInstanceHandles = require('ReactInstanceHandles');
+
+var containsNode = require('containsNode');
+var invariant = require('invariant');
+
+var SEPARATOR = ReactInstanceHandles.SEPARATOR;
+
+var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
+var nodeCache = {};
+
+var ELEMENT_NODE_TYPE = 1;
+var DOC_NODE_TYPE = 9;
+
+var idSeed = 0;
+// Like getElementById(), except supports elements not in the document.
+var reactContainers = {};
+
+/** Mapping from reactRootID to `container` nodes. */
+var containersByReactRootID = {};
+
+function registerContainerDOMNode(domNode) {
+ reactContainers[domNode.id] = domNode;
+}
+
+// Used to store breadth-first search state in findComponentRoot.
+var findComponentRootReusableArray = [];
+
+if (__DEV__) {
+ /** __DEV__-only mapping from reactRootID to root elements. */
+ var rootElementsByReactRootID = {};
+}
+
+function getReactRootID(container) {
+ return getID(container.firstChild);
+}
+
+/**
+ * Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
+ * element can return its control whose name or ID equals ATTR_NAME. All
+ * DOM nodes support `getAttributeNode` but this can also get called on
+ * other objects so just return '' if we're given something other than a
+ * DOM node (such as window).
+ *
+ * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
+ * @return {string} ID of the supplied `domNode`.
+ */
+function getID(node) {
+ var id = internalGetID(node);
+ if (id) {
+ if (nodeCache.hasOwnProperty(id)) {
+ var cached = nodeCache[id];
+ if (cached !== node) {
+ invariant(
+ !isValid(cached, id),
+ 'ReactDOMNodeMapping: Two valid but unequal nodes with the same `%s`: %s',
+ ATTR_NAME, id
+ );
+
+ nodeCache[id] = node;
+ }
+ } else {
+ nodeCache[id] = node;
+ }
+ }
+
+ return id;
+}
+
+function internalGetID(node) {
+ // If node is something like a window, document, or text node, none of
+ // which support attributes or a .getAttribute method, gracefully return
+ // the empty string, as if the attribute were missing.
+ return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
+}
+
+/**
+ * Sets the React-specific ID of the given node.
+ *
+ * @param {DOMElement} node The DOM node whose ID will be set.
+ * @param {string} id The value of the ID attribute.
+ */
+function setID(node, id) {
+ var oldID = internalGetID(node);
+ if (oldID !== id) {
+ delete nodeCache[oldID];
+ }
+ node.setAttribute(ATTR_NAME, id);
+ nodeCache[id] = node;
+}
+
+/**
+ * Finds the node with the supplied React-generated DOM ID.
+ *
+ * @param {string} id A React-generated DOM ID.
+ * @return {DOMElement} DOM node with the suppled `id`.
+ * @internal
+ */
+function getNode(id) {
+ if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
+ nodeCache[id] = ReactDOMNodeMapping.findReactNodeByID(id);
+ }
+ return nodeCache[id];
+}
+
+/**
+ * A node is "valid" if it is contained by a currently mounted container.
+ *
+ * This means that the node does not have to be contained by a document in
+ * order to be considered valid.
+ *
+ * @param {?DOMElement} node The candidate DOM node.
+ * @param {string} id The expected ID of the node.
+ * @return {boolean} Whether the node is contained by a mounted container.
+ */
+function isValid(node, id) {
+ if (node) {
+ invariant(
+ internalGetID(node) === id,
+ 'ReactDOMNodeMapping: Unexpected modification of `%s`',
+ ATTR_NAME
+ );
+
+ var container = ReactDOMNodeMapping.findReactContainerForID(id);
+ if (container && containsNode(container, node)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Causes the cache to forget about one React-specific ID.
+ *
+ * @param {string} id The ID to forget.
+ */
+function purgeID(id) {
+ delete nodeCache[id];
+}
+
+var deepestNodeSoFar = null;
+function findDeepestCachedAncestorImpl(ancestorID) {
+ var ancestor = nodeCache[ancestorID];
+ if (ancestor && isValid(ancestor, ancestorID)) {
+ deepestNodeSoFar = ancestor;
+ } else {
+ // This node isn't populated in the cache, so presumably none of its
+ // descendants are. Break out of the loop.
+ return false;
+ }
+}
+
+/**
+ * Return the deepest cached node whose ID is a prefix of `targetID`.
+ */
+function findDeepestCachedAncestor(targetID) {
+ deepestNodeSoFar = null;
+ ReactInstanceHandles.traverseAncestors(
+ targetID,
+ findDeepestCachedAncestorImpl
+ );
+
+ var foundNode = deepestNodeSoFar;
+ deepestNodeSoFar = null;
+ return foundNode;
+}
+
+/**
+ * Mounting is the process of initializing a React component by creatings its
+ * representative DOM elements and inserting them into a supplied `container`.
+ * Any prior content inside `container` is destroyed in the process.
+ *
+ * ReactMount.renderComponent(
+ * component,
+ * document.getElementById('container')
+ * );
+ *
+ * <-- Supplied `container`.
+ *
<-- Rendered reactRoot of React
+ * // ... component.
+ *
+ *
+ *
+ * Inside of `container`, the first element rendered is the "reactRoot".
+ */
+var ReactDOMNodeMapping = {
+ /** Time spent generating markup. */
+ totalInstantiationTime: 0,
+
+ /** Time spent inserting markup into the DOM. */
+ totalInjectionTime: 0,
+
+ /** Whether support for touch events should be initialized. */
+ useTouchEvents: false,
+
+ getReactRootElementInContainer: function(container) {
+ if (!container) {
+ return null;
+ }
+
+ if (container.nodeType === DOC_NODE_TYPE) {
+ return container.documentElement;
+ } else {
+ return container.firstChild;
+ }
+ },
+
+ /**
+ * Register a component into the instance map and starts scroll value
+ * monitoring
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {DOMElement} container container to render into
+ * @return {string} reactRoot ID prefix
+ */
+ registerComponentInContainer: function(reactRootID, containerHandle) {
+ var container = ReactDOMNodeMapping.resolveDOMNodeHandle(containerHandle);
+
+ invariant(
+ container && (
+ container.nodeType === ELEMENT_NODE_TYPE ||
+ container.nodeType === DOC_NODE_TYPE
+ ),
+ '_registerComponent(...): Target container is not a DOM element.'
+ );
+
+ ReactEventEmitter.ensureScrollValueMonitoring();
+
+ containersByReactRootID[reactRootID] = container;
+ return reactRootID;
+ },
+
+ /**
+ * Finds the container DOM element that contains React component to which the
+ * supplied DOM `id` belongs.
+ *
+ * @param {string} id The ID of an element rendered by a React component.
+ * @return {?DOMElement} DOM element that contains the `id`.
+ */
+ findReactContainerForID: function(id) {
+ var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
+ var container = containersByReactRootID[reactRootID];
+
+ if (__DEV__) {
+ var rootElement = rootElementsByReactRootID[reactRootID];
+ if (rootElement && rootElement.parentNode !== container) {
+ invariant(
+ // Call internalGetID here because getID calls isValid which calls
+ // findReactContainerForID (this function).
+ internalGetID(rootElement) === reactRootID,
+ 'ReactDOMNodeMapping: Root element ID differed from reactRootID.'
+ );
+
+ var containerChild = container.firstChild;
+ if (containerChild &&
+ reactRootID === internalGetID(containerChild)) {
+ // If the container has a new child with the same ID as the old
+ // root element, then rootElementsByReactRootID[reactRootID] is
+ // just stale and needs to be updated. The case that deserves a
+ // warning is when the container is empty.
+ rootElementsByReactRootID[reactRootID] = containerChild;
+ } else {
+ console.warn(
+ 'ReactDOMNodeMapping: Root element has been removed from its original ' +
+ 'container. New container:', rootElement.parentNode
+ );
+ }
+ }
+ }
+
+ return container;
+ },
+
+ /**
+ * Finds an element rendered by React with the supplied ID.
+ *
+ * @param {string} id ID of a DOM node in the React component.
+ * @return {DOMElement} Root DOM node of the React component.
+ */
+ findReactNodeByID: function(id) {
+ var reactRoot = ReactDOMNodeMapping.findReactContainerForID(id);
+ return ReactDOMNodeMapping.findComponentRoot(reactRoot, id);
+ },
+
+ /**
+ * True if the supplied `node` is rendered by React.
+ *
+ * @param {*} node DOM Element to check.
+ * @return {boolean} True if the DOM Element appears to be rendered by React.
+ * @internal
+ */
+ isRenderedByReact: function(node) {
+ if (node.nodeType !== 1) {
+ // Not a DOMElement, therefore not a React component
+ return false;
+ }
+ var id = ReactDOMNodeMapping.getID(node);
+ return id ? id.charAt(0) === SEPARATOR : false;
+ },
+
+ /**
+ * Traverses up the ancestors of the supplied node to find a node that is a
+ * DOM representation of a React component.
+ *
+ * @param {*} node
+ * @return {?DOMEventTarget}
+ * @internal
+ */
+ getFirstReactDOM: function(node) {
+ var current = node;
+ while (current && current.parentNode !== current) {
+ if (ReactDOMNodeMapping.isRenderedByReact(current)) {
+ return current;
+ }
+ current = current.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Finds a node with the supplied `targetID` inside of the supplied
+ * `ancestorNode`. Exploits the ID naming scheme to perform the search
+ * quickly.
+ *
+ * @param {DOMEventTarget} ancestorNode Search from this root.
+ * @pararm {string} targetID ID of the DOM representation of the component.
+ * @return {DOMEventTarget} DOM node with the supplied `targetID`.
+ * @internal
+ */
+ findComponentRoot: function(ancestorNode, targetID) {
+ var firstChildren = findComponentRootReusableArray;
+ var childIndex = 0;
+
+ var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode;
+
+ firstChildren[0] = deepestAncestor.firstChild;
+ firstChildren.length = 1;
+
+ while (childIndex < firstChildren.length) {
+ var child = firstChildren[childIndex++];
+ var targetChild;
+
+ while (child) {
+ var childID = ReactDOMNodeMapping.getID(child);
+ if (childID) {
+ // Even if we find the node we're looking for, we finish looping
+ // through its siblings to ensure they're cached so that we don't have
+ // to revisit this node again. Otherwise, we make n^2 calls to getID
+ // when visiting the many children of a single node in order.
+
+ if (targetID === childID) {
+ targetChild = child;
+ } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) {
+ // If we find a child whose ID is an ancestor of the given ID,
+ // then we can be sure that we only want to search the subtree
+ // rooted at this child, so we can throw out the rest of the
+ // search state.
+ firstChildren.length = childIndex = 0;
+ firstChildren.push(child.firstChild);
+ }
+
+ } else {
+ // If this child had no ID, then there's a chance that it was
+ // injected automatically by the browser, as when a ``
+ // element sprouts an extra `` child as a side effect of
+ // `.innerHTML` parsing. Optimistically continue down this
+ // branch, but not before examining the other siblings.
+ firstChildren.push(child.firstChild);
+ }
+
+ child = child.nextSibling;
+ }
+
+ if (targetChild) {
+ // Emptying firstChildren/findComponentRootReusableArray is
+ // not necessary for correctness, but it helps the GC reclaim
+ // any nodes that were left at the end of the search.
+ firstChildren.length = 0;
+
+ return targetChild;
+ }
+ }
+
+ firstChildren.length = 0;
+
+ invariant(
+ false,
+ 'findComponentRoot(..., %s): Unable to find element. This probably ' +
+ 'means the DOM was unexpectedly mutated (e.g., by the browser), ' +
+ 'usually due to forgetting a when using tables or nesting ' +
+ 'or tags. Try inspecting the child nodes of the element with React ' +
+ 'ID `%s`.',
+ targetID,
+ ReactDOMNodeMapping.getID(ancestorNode)
+ );
+ },
+
+ // Called remotely
+ unmountComponentAtHandle: function(containerHandle) {
+ ReactDOMNodeMapping.unmountComponentAtNode(
+ ReactDOMNodeMapping.resolveDOMNodeHandle(containerHandle)
+ );
+ },
+
+ /**
+ * Unmounts and destroys the React component rendered in the `container`.
+ *
+ * @param {DOMElement} container DOM element containing a React component.
+ * @return {boolean} True if a component was found in and unmounted from
+ * `container`
+ */
+ unmountComponentAtNode: function(container) {
+ invariant(
+ container && (
+ container.nodeType === ELEMENT_NODE_TYPE ||
+ container.nodeType === DOC_NODE_TYPE
+ ),
+ 'unmountComponentAtNode(...): Target container is not a DOM element.'
+ );
+
+ var containerHandle = ReactDOMNodeMapping.getHandleForContainer(container);
+ delete reactContainers[containerHandle.id];
+
+ var reactRootID = ReactDOMNodeHandleMapping.unmountComponentAtNode(containerHandle);
+ if (__DEV__) {
+ delete rootElementsByReactRootID[reactRootID];
+ }
+ if (reactRootID) {
+ delete containersByReactRootID[reactRootID]
+ ReactDOMNodeMapping._unmountNode(container);
+ }
+ },
+
+ /**
+ * Unmounts a component and removes it from the DOM.
+ *
+ * @param {ReactComponent} instance React component instance.
+ * @param {DOMElement} container DOM element to unmount from.
+ * @final
+ * @internal
+ * @see {ReactDOMNodeMapping.unmountComponentAtNode}
+ */
+ _unmountNode: function(container) {
+ if (container.nodeType === DOC_NODE_TYPE) {
+ container = container.documentElement;
+ }
+
+ // http://jsperf.com/emptying-a-node
+ while (container.lastChild) {
+ container.removeChild(container.lastChild);
+ }
+ },
+
+ getInstanceFromContainer: function(container) {
+ invariant(
+ container && (
+ container.nodeType === ELEMENT_NODE_TYPE ||
+ container.nodeType === DOC_NODE_TYPE
+ ),
+ 'getInstanceFromContainer(...): Target container is not a DOM element.'
+ );
+
+ return ReactDOMNodeHandleMapping.getInstanceFromContainer(
+ ReactDOMNodeMapping.getHandleForContainer(container)
+ );
+ },
+
+ recordRootElementForTransplantWarning: function(container) {
+ if (__DEV__) {
+ // Record the root element in case it later gets transplanted.
+ rootElementsByReactRootID[getReactRootID(container)] =
+ ReactDOMNodeMapping.getReactRootElementInContainer(container);
+ }
+ },
+
+ resolveDOMNodeHandle: function(handle) {
+ invariant(
+ ExecutionEnvironment.canUseDOM,
+ 'Cannot resolveHandle() in a worker!'
+ );
+
+
+ if (handle.type === ReactDOMNodeHandleTypes.REACT_ID_TOP_LEVEL) {
+ var container = ReactDOMNodeMapping.findReactContainerForID(handle.reactID);
+ if (container) {
+ return container.nodeType === ELEMENT_NODE_TYPE ?
+ container.ownerDocument :
+ container;
+ }
+ return null;
+ } else if (handle.type === ReactDOMNodeHandleTypes.REACT_ID) {
+ return ReactDOMNodeMapping.getNode(handle.reactID);
+ } else {
+ invariant(
+ handle.type === ReactDOMNodeHandleTypes.CONTAINER,
+ 'Invalid handle type: %s',
+ handle.type
+ );
+ var container = reactContainers[handle.id];
+ if (!container) {
+ container = reactContainers[handle.id] = document.getElementById(
+ handle.id
+ );
+ }
+ return container;
+ }
+ },
+
+ getHandleForContainer: function(domNode) {
+ if (!domNode.id) {
+ domNode.id = '.rC_' + (idSeed++);
+ }
+
+ registerContainerDOMNode(domNode);
+
+ return ReactDOMNodeHandle.getHandleForContainerID(domNode.id);
+ },
+
+ // Called remotely; see ReactDOMNodeMappingRemote
+ registerContainerHandle: function(handle) {
+ var domNode = ReactDOMNodeMapping.resolveDOMNodeHandle(handle);
+ registerContainerDOMNode(domNode);
+ },
+
+ /**
+ * React ID utilities.
+ */
+
+ getReactRootID: getReactRootID,
+
+ getID: getID,
+
+ setID: setID,
+
+ getNode: getNode,
+
+ purgeID: purgeID
+};
+
+module.exports = ReactDOMNodeMapping;
diff --git a/src/browser/ui/ReactDefaultInjection.js b/src/browser/ui/ReactDefaultInjection.js
index a981a93f9b90d..4734943d5afb2 100644
--- a/src/browser/ui/ReactDefaultInjection.js
+++ b/src/browser/ui/ReactDefaultInjection.js
@@ -18,6 +18,19 @@
"use strict";
+var DOMProperty = require('DOMProperty');
+var EventPluginHub = require('EventPluginHub');
+var ReactComponent = require('ReactComponent');
+var ReactCompositeComponent = require('ReactCompositeComponent');
+var ReactDOM = require('ReactDOM');
+var ReactEmptyComponent = require('ReactEmptyComponent');
+var ReactEventEmitter = require('ReactEventEmitter');
+var ReactPerf = require('ReactPerf');
+var ReactRootIndex = require('ReactRootIndex');
+var ReactUpdates = require('ReactUpdates');
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+
var BeforeInputEventPlugin = require('BeforeInputEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var ClientReactRootIndex = require('ClientReactRootIndex');
@@ -31,6 +44,7 @@ var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
+var ReactEventListener = require('ReactEventListener');
var ReactDOM = require('ReactDOM');
var ReactDOMButton = require('ReactDOMButton');
var ReactDOMForm = require('ReactDOMForm');
@@ -39,10 +53,8 @@ var ReactDOMInput = require('ReactDOMInput');
var ReactDOMOption = require('ReactDOMOption');
var ReactDOMSelect = require('ReactDOMSelect');
var ReactDOMTextarea = require('ReactDOMTextarea');
-var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
-var ReactInjection = require('ReactInjection');
var ReactInstanceHandles = require('ReactInstanceHandles');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var SelectEventPlugin = require('SelectEventPlugin');
var ServerReactRootIndex = require('ServerReactRootIndex');
var SimpleEventPlugin = require('SimpleEventPlugin');
@@ -51,22 +63,22 @@ var SVGDOMPropertyConfig = require('SVGDOMPropertyConfig');
var createFullPageComponent = require('createFullPageComponent');
function inject() {
- ReactInjection.EventEmitter.injectTopLevelCallbackCreator(
- ReactEventTopLevelCallback
+ ReactEventEmitter.injection.injectReactEventListener(
+ ReactEventListener
);
/**
* Inject modules for resolving DOM hierarchy and plugin ordering.
*/
- ReactInjection.EventPluginHub.injectEventPluginOrder(DefaultEventPluginOrder);
- ReactInjection.EventPluginHub.injectInstanceHandle(ReactInstanceHandles);
- ReactInjection.EventPluginHub.injectMount(ReactMount);
+ EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
+ EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
+ EventPluginHub.injection.injectMount(ReactDOMNodeMapping);
/**
* Some important event plugins included by default (without having to require
* them).
*/
- ReactInjection.EventPluginHub.injectEventPluginsByName({
+ EventPluginHub.injection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
@@ -76,7 +88,7 @@ function inject() {
BeforeInputEventPlugin: BeforeInputEventPlugin
});
- ReactInjection.DOM.injectComponentClasses({
+ ReactDOM.injection.injectComponentClasses({
button: ReactDOMButton,
form: ReactDOMForm,
img: ReactDOMImg,
@@ -93,27 +105,27 @@ function inject() {
// This needs to happen after createFullPageComponent() otherwise the mixin
// gets double injected.
- ReactInjection.CompositeComponent.injectMixin(ReactBrowserComponentMixin);
+ ReactCompositeComponent.injection.injectMixin(ReactBrowserComponentMixin);
- ReactInjection.DOMProperty.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
- ReactInjection.DOMProperty.injectDOMPropertyConfig(SVGDOMPropertyConfig);
+ DOMProperty.injection.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
+ DOMProperty.injection.injectDOMPropertyConfig(SVGDOMPropertyConfig);
- ReactInjection.EmptyComponent.injectEmptyComponent(ReactDOM.script);
+ ReactEmptyComponent.injection.injectEmptyComponent(ReactDOM.script);
- ReactInjection.Updates.injectReconcileTransaction(
+ ReactUpdates.injection.injectReconcileTransaction(
ReactComponentBrowserEnvironment.ReactReconcileTransaction
);
- ReactInjection.Updates.injectBatchingStrategy(
+ ReactUpdates.injection.injectBatchingStrategy(
ReactDefaultBatchingStrategy
);
- ReactInjection.RootIndex.injectCreateReactRootIndex(
+ ReactRootIndex.injection.injectCreateReactRootIndex(
ExecutionEnvironment.canUseDOM ?
ClientReactRootIndex.createReactRootIndex :
ServerReactRootIndex.createReactRootIndex
);
- ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
+ ReactComponent.injection.injectEnvironment(ReactComponentBrowserEnvironment);
if (__DEV__) {
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
diff --git a/src/browser/ui/ReactEventListener.js b/src/browser/ui/ReactEventListener.js
new file mode 100644
index 0000000000000..68faf57374271
--- /dev/null
+++ b/src/browser/ui/ReactEventListener.js
@@ -0,0 +1,183 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactEventListener
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var EventListener = require('EventListener');
+var PooledClass = require('PooledClass');
+var ReactInstanceHandles = require('ReactInstanceHandles');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
+var ReactUpdates = require('ReactUpdates');
+
+var getEventTarget = require('getEventTarget');
+var getUnboundedScrollPosition = require('getUnboundedScrollPosition');
+var invariant = require('invariant');
+var mixInto = require('mixInto');
+
+/**
+ * Finds the parent React component of `node`.
+ *
+ * @param {*} node
+ * @return {?DOMEventTarget} Parent container, or `null` if the specified node
+ * is not nested.
+ */
+function findParent(node) {
+ // TODO: It may be a good idea to cache this to prevent unnecessary DOM
+ // traversal, but caching is difficult to do correctly without using a
+ // mutation observer to listen for all DOM changes.
+ var nodeID = ReactDOMNodeMapping.getID(node);
+ var rootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID);
+ var container = ReactDOMNodeMapping.findReactContainerForID(rootID);
+ var parent = ReactDOMNodeMapping.getFirstReactDOM(container);
+ return parent;
+}
+
+// Used to store ancestor hierarchy in top level callback
+function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
+ this.topLevelType = topLevelType;
+ this.nativeEvent = nativeEvent;
+ this.ancestors = [];
+}
+mixInto(TopLevelCallbackBookKeeping, {
+ destructor: function() {
+ this.topLevelType = null;
+ this.nativeEvent = null;
+ this.ancestors.length = 0;
+ }
+});
+PooledClass.addPoolingTo(
+ TopLevelCallbackBookKeeping,
+ PooledClass.twoArgumentPooler
+);
+
+function handleTopLevelImpl(bookKeeping) {
+ var topLevelTarget = ReactDOMNodeMapping.getFirstReactDOM(
+ getEventTarget(bookKeeping.nativeEvent)
+ ) || window;
+
+ // Loop through the hierarchy, in case there's any nested components.
+ // It's important that we build the array of ancestors before calling any
+ // event handlers, because event handlers can modify the DOM, leading to
+ // inconsistencies with ReactDOMNodeMapping's node cache. See #1105.
+ var ancestor = topLevelTarget;
+ while (ancestor) {
+ bookKeeping.ancestors.push(ancestor);
+ ancestor = findParent(ancestor);
+ }
+
+ for (var i = 0, l = bookKeeping.ancestors.length; i < l; i++) {
+ topLevelTarget = bookKeeping.ancestors[i];
+ var topLevelTargetID = ReactDOMNodeMapping.getID(topLevelTarget) || '';
+ ReactEventListener._handleTopLevel(
+ bookKeeping.topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ bookKeeping.nativeEvent
+ );
+ }
+}
+
+function scrollValueMonitor(cb) {
+ var scrollPosition = getUnboundedScrollPosition(window);
+ cb(scrollPosition);
+}
+
+var ReactEventListener = {
+ _enabled: true,
+ _handleTopLevel: null,
+
+ setHandleTopLevel: function(handleTopLevel) {
+ ReactEventListener._handleTopLevel = handleTopLevel;
+ },
+
+ setEnabled: function(enabled) {
+ ReactEventListener._enabled = !!enabled;
+ },
+
+ isEnabled: function() {
+ return ReactEventListener._enabled;
+ },
+
+
+ /**
+ * Traps top-level events by using event bubbling.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {string} handlerBaseName Event name (e.g. "click").
+ * @param {object} handle Element on which to attach listener.
+ * @internal
+ */
+ trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
+ var element = ReactDOMNodeMapping.resolveDOMNodeHandle(handle);
+ if (!element) {
+ return;
+ }
+ EventListener.listen(
+ element,
+ handlerBaseName,
+ ReactEventListener.dispatchEvent.bind(null, topLevelType)
+ );
+ },
+
+ /**
+ * Traps a top-level event by using event capturing.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {string} handlerBaseName Event name (e.g. "click").
+ * @param {object} handle Element on which to attach listener.
+ * @internal
+ */
+ trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
+ var element = ReactDOMNodeMapping.resolveDOMNodeHandle(handle);
+ if (!element) {
+ return;
+ }
+ EventListener.capture(
+ element,
+ handlerBaseName,
+ ReactEventListener.dispatchEvent.bind(null, topLevelType)
+ );
+ },
+
+ monitorScrollValue: function(refresh) {
+ var callback = scrollValueMonitor.bind(null, refresh);
+ EventListener.listen(window, 'scroll', callback);
+ EventListener.listen(window, 'resize', callback);
+ },
+
+ dispatchEvent: function(topLevelType, nativeEvent) {
+ if (!ReactEventListener._enabled) {
+ return;
+ }
+
+ var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
+ topLevelType,
+ nativeEvent
+ );
+ try {
+ // Event queue being processed in the same cycle allows
+ // `preventDefault`.
+ ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
+ } finally {
+ TopLevelCallbackBookKeeping.release(bookKeeping);
+ }
+ }
+};
+
+module.exports = ReactEventListener;
diff --git a/src/browser/ui/ReactEventTopLevelCallback.js b/src/browser/ui/ReactEventTopLevelCallback.js
deleted file mode 100644
index aef2eef09fa62..0000000000000
--- a/src/browser/ui/ReactEventTopLevelCallback.js
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * Copyright 2013-2014 Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @providesModule ReactEventTopLevelCallback
- * @typechecks static-only
- */
-
-"use strict";
-
-var PooledClass = require('PooledClass');
-var ReactEventEmitter = require('ReactEventEmitter');
-var ReactInstanceHandles = require('ReactInstanceHandles');
-var ReactMount = require('ReactMount');
-var ReactUpdates = require('ReactUpdates');
-
-var getEventTarget = require('getEventTarget');
-var mixInto = require('mixInto');
-
-/**
- * @type {boolean}
- * @private
- */
-var _topLevelListenersEnabled = true;
-
-/**
- * Finds the parent React component of `node`.
- *
- * @param {*} node
- * @return {?DOMEventTarget} Parent container, or `null` if the specified node
- * is not nested.
- */
-function findParent(node) {
- // TODO: It may be a good idea to cache this to prevent unnecessary DOM
- // traversal, but caching is difficult to do correctly without using a
- // mutation observer to listen for all DOM changes.
- var nodeID = ReactMount.getID(node);
- var rootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID);
- var container = ReactMount.findReactContainerForID(rootID);
- var parent = ReactMount.getFirstReactDOM(container);
- return parent;
-}
-
-/**
- * Calls ReactEventEmitter.handleTopLevel for each node stored in bookKeeping's
- * ancestor list. Separated from createTopLevelCallback to avoid try/finally
- * deoptimization.
- *
- * @param {TopLevelCallbackBookKeeping} bookKeeping
- */
-function handleTopLevelImpl(bookKeeping) {
- var topLevelTarget = ReactMount.getFirstReactDOM(
- getEventTarget(bookKeeping.nativeEvent)
- ) || window;
-
- // Loop through the hierarchy, in case there's any nested components.
- // It's important that we build the array of ancestors before calling any
- // event handlers, because event handlers can modify the DOM, leading to
- // inconsistencies with ReactMount's node cache. See #1105.
- var ancestor = topLevelTarget;
- while (ancestor) {
- bookKeeping.ancestors.push(ancestor);
- ancestor = findParent(ancestor);
- }
-
- for (var i = 0, l = bookKeeping.ancestors.length; i < l; i++) {
- topLevelTarget = bookKeeping.ancestors[i];
- var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
- ReactEventEmitter.handleTopLevel(
- bookKeeping.topLevelType,
- topLevelTarget,
- topLevelTargetID,
- bookKeeping.nativeEvent
- );
- }
-}
-
-// Used to store ancestor hierarchy in top level callback
-function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
- this.topLevelType = topLevelType;
- this.nativeEvent = nativeEvent;
- this.ancestors = [];
-}
-mixInto(TopLevelCallbackBookKeeping, {
- destructor: function() {
- this.topLevelType = null;
- this.nativeEvent = null;
- this.ancestors.length = 0;
- }
-});
-PooledClass.addPoolingTo(
- TopLevelCallbackBookKeeping,
- PooledClass.twoArgumentPooler
-);
-
-/**
- * Top-level callback creator used to implement event handling using delegation.
- * This is used via dependency injection.
- */
-var ReactEventTopLevelCallback = {
-
- /**
- * Sets whether or not any created callbacks should be enabled.
- *
- * @param {boolean} enabled True if callbacks should be enabled.
- */
- setEnabled: function(enabled) {
- _topLevelListenersEnabled = !!enabled;
- },
-
- /**
- * @return {boolean} True if callbacks are enabled.
- */
- isEnabled: function() {
- return _topLevelListenersEnabled;
- },
-
- /**
- * Creates a callback for the supplied `topLevelType` that could be added as
- * a listener to the document. The callback computes a `topLevelTarget` which
- * should be the root node of a mounted React component where the listener
- * is attached.
- *
- * @param {string} topLevelType Record from `EventConstants`.
- * @return {function} Callback for handling top-level events.
- */
- createTopLevelCallback: function(topLevelType) {
- return function(nativeEvent) {
- if (!_topLevelListenersEnabled) {
- return;
- }
-
- var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
- topLevelType,
- nativeEvent
- );
- try {
- // Event queue being processed in the same cycle allows
- // `preventDefault`.
- ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
- } finally {
- TopLevelCallbackBookKeeping.release(bookKeeping);
- }
- };
- }
-
-};
-
-module.exports = ReactEventTopLevelCallback;
diff --git a/src/browser/ui/ReactInjection.js b/src/browser/ui/ReactInjection.js
deleted file mode 100644
index c51a2128003cc..0000000000000
--- a/src/browser/ui/ReactInjection.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * Copyright 2013-2014 Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @providesModule ReactInjection
- */
-
-"use strict";
-
-var DOMProperty = require('DOMProperty');
-var EventPluginHub = require('EventPluginHub');
-var ReactComponent = require('ReactComponent');
-var ReactCompositeComponent = require('ReactCompositeComponent');
-var ReactDOM = require('ReactDOM');
-var ReactEmptyComponent = require('ReactEmptyComponent');
-var ReactEventEmitter = require('ReactEventEmitter');
-var ReactPerf = require('ReactPerf');
-var ReactRootIndex = require('ReactRootIndex');
-var ReactUpdates = require('ReactUpdates');
-
-var ReactInjection = {
- Component: ReactComponent.injection,
- CompositeComponent: ReactCompositeComponent.injection,
- DOMProperty: DOMProperty.injection,
- EmptyComponent: ReactEmptyComponent.injection,
- EventPluginHub: EventPluginHub.injection,
- DOM: ReactDOM.injection,
- EventEmitter: ReactEventEmitter.injection,
- Perf: ReactPerf.injection,
- RootIndex: ReactRootIndex.injection,
- Updates: ReactUpdates.injection
-};
-
-module.exports = ReactInjection;
diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js
index e898b5899488f..22ea3e0a44187 100644
--- a/src/browser/ui/ReactMount.js
+++ b/src/browser/ui/ReactMount.js
@@ -18,271 +18,47 @@
"use strict";
-var DOMProperty = require('DOMProperty');
var ReactCurrentOwner = require('ReactCurrentOwner');
-var ReactEventEmitter = require('ReactEventEmitter');
-var ReactInstanceHandles = require('ReactInstanceHandles');
+var ReactDOMNodeHandleMapping = require('ReactDOMNodeHandleMapping');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactPerf = require('ReactPerf');
-var containsNode = require('containsNode');
-var getReactRootElementInContainer = require('getReactRootElementInContainer');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
-var SEPARATOR = ReactInstanceHandles.SEPARATOR;
-
-var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
-var nodeCache = {};
-
-var ELEMENT_NODE_TYPE = 1;
-var DOC_NODE_TYPE = 9;
-
-/** Mapping from reactRootID to React component instance. */
-var instancesByReactRootID = {};
-
-/** Mapping from reactRootID to `container` nodes. */
-var containersByReactRootID = {};
-
-if (__DEV__) {
- /** __DEV__-only mapping from reactRootID to root elements. */
- var rootElementsByReactRootID = {};
-}
-
-// Used to store breadth-first search state in findComponentRoot.
-var findComponentRootReusableArray = [];
-
-/**
- * @param {DOMElement} container DOM element that may contain a React component.
- * @return {?string} A "reactRoot" ID, if a React component is rendered.
- */
-function getReactRootID(container) {
- var rootElement = getReactRootElementInContainer(container);
- return rootElement && ReactMount.getID(rootElement);
-}
-
-/**
- * Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
- * element can return its control whose name or ID equals ATTR_NAME. All
- * DOM nodes support `getAttributeNode` but this can also get called on
- * other objects so just return '' if we're given something other than a
- * DOM node (such as window).
- *
- * @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
- * @return {string} ID of the supplied `domNode`.
- */
-function getID(node) {
- var id = internalGetID(node);
- if (id) {
- if (nodeCache.hasOwnProperty(id)) {
- var cached = nodeCache[id];
- if (cached !== node) {
- invariant(
- !isValid(cached, id),
- 'ReactMount: Two valid but unequal nodes with the same `%s`: %s',
- ATTR_NAME, id
- );
-
- nodeCache[id] = node;
- }
- } else {
- nodeCache[id] = node;
- }
- }
-
- return id;
-}
-
-function internalGetID(node) {
- // If node is something like a window, document, or text node, none of
- // which support attributes or a .getAttribute method, gracefully return
- // the empty string, as if the attribute were missing.
- return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
-}
-
-/**
- * Sets the React-specific ID of the given node.
- *
- * @param {DOMElement} node The DOM node whose ID will be set.
- * @param {string} id The value of the ID attribute.
- */
-function setID(node, id) {
- var oldID = internalGetID(node);
- if (oldID !== id) {
- delete nodeCache[oldID];
- }
- node.setAttribute(ATTR_NAME, id);
- nodeCache[id] = node;
-}
-
-/**
- * Finds the node with the supplied React-generated DOM ID.
- *
- * @param {string} id A React-generated DOM ID.
- * @return {DOMElement} DOM node with the suppled `id`.
- * @internal
- */
-function getNode(id) {
- if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
- nodeCache[id] = ReactMount.findReactNodeByID(id);
- }
- return nodeCache[id];
-}
-
-/**
- * A node is "valid" if it is contained by a currently mounted container.
- *
- * This means that the node does not have to be contained by a document in
- * order to be considered valid.
- *
- * @param {?DOMElement} node The candidate DOM node.
- * @param {string} id The expected ID of the node.
- * @return {boolean} Whether the node is contained by a mounted container.
- */
-function isValid(node, id) {
- if (node) {
- invariant(
- internalGetID(node) === id,
- 'ReactMount: Unexpected modification of `%s`',
- ATTR_NAME
- );
-
- var container = ReactMount.findReactContainerForID(id);
- if (container && containsNode(container, node)) {
- return true;
- }
- }
-
- return false;
-}
-
-/**
- * Causes the cache to forget about one React-specific ID.
- *
- * @param {string} id The ID to forget.
- */
-function purgeID(id) {
- delete nodeCache[id];
-}
-
-var deepestNodeSoFar = null;
-function findDeepestCachedAncestorImpl(ancestorID) {
- var ancestor = nodeCache[ancestorID];
- if (ancestor && isValid(ancestor, ancestorID)) {
- deepestNodeSoFar = ancestor;
- } else {
- // This node isn't populated in the cache, so presumably none of its
- // descendants are. Break out of the loop.
- return false;
- }
-}
-
-/**
- * Return the deepest cached node whose ID is a prefix of `targetID`.
- */
-function findDeepestCachedAncestor(targetID) {
- deepestNodeSoFar = null;
- ReactInstanceHandles.traverseAncestors(
- targetID,
- findDeepestCachedAncestorImpl
- );
-
- var foundNode = deepestNodeSoFar;
- deepestNodeSoFar = null;
- return foundNode;
-}
-
-/**
- * Mounting is the process of initializing a React component by creatings its
- * representative DOM elements and inserting them into a supplied `container`.
- * Any prior content inside `container` is destroyed in the process.
- *
- * ReactMount.renderComponent(
- * component,
- * document.getElementById('container')
- * );
- *
- * <-- Supplied `container`.
- *
<-- Rendered reactRoot of React
- * // ... component.
- *
- *
- *
- * Inside of `container`, the first element rendered is the "reactRoot".
- */
var ReactMount = {
- /** Time spent generating markup. */
- totalInstantiationTime: 0,
-
- /** Time spent inserting markup into the DOM. */
- totalInjectionTime: 0,
-
- /** Whether support for touch events should be initialized. */
- useTouchEvents: false,
-
- /** Exposed for debugging purposes **/
- _instancesByReactRootID: instancesByReactRootID,
-
/**
- * This is a hook provided to support rendering React components while
- * ensuring that the apparent scroll position of its `container` does not
- * change.
+ * Constructs a component instance of `constructor` with `initialProps` and
+ * renders it into the supplied `container`.
*
- * @param {DOMElement} container The `container` being rendered into.
- * @param {function} renderCallback This must be called once to do the render.
- */
- scrollMonitor: function(container, renderCallback) {
- renderCallback();
- },
-
- /**
- * Take a component that's already mounted into the DOM and replace its props
- * @param {ReactComponent} prevComponent component instance already in the DOM
- * @param {ReactComponent} nextComponent component instance to render
- * @param {DOMElement} container container to render into
- * @param {?function} callback function triggered on completion
+ * @param {function} constructor React component constructor.
+ * @param {?object} props Initial props of the component instance.
+ * @param {DOMElement} container DOM element to render into.
+ * @return {ReactComponent} Component instance rendered in `container`.
*/
- _updateRootComponent: function(
- prevComponent,
- nextComponent,
- container,
- callback) {
- var nextProps = nextComponent.props;
- ReactMount.scrollMonitor(container, function() {
- prevComponent.replaceProps(nextProps, callback);
- });
-
- if (__DEV__) {
- // Record the root element in case it later gets transplanted.
- rootElementsByReactRootID[getReactRootID(container)] =
- getReactRootElementInContainer(container);
- }
-
- return prevComponent;
+ constructAndRenderComponent: function(constructor, props, container) {
+ return ReactMount.renderComponent(constructor(props), container);
},
/**
- * Register a component into the instance map and starts scroll value
- * monitoring
- * @param {ReactComponent} nextComponent component instance to render
- * @param {DOMElement} container container to render into
- * @return {string} reactRoot ID prefix
+ * Constructs a component instance of `constructor` with `initialProps` and
+ * renders it into a container node identified by supplied `id`.
+ *
+ * @param {function} componentConstructor React component constructor
+ * @param {?object} props Initial props of the component instance.
+ * @param {string} id ID of the DOM element to render into.
+ * @return {ReactComponent} Component instance rendered in the container node.
*/
- _registerComponent: function(nextComponent, container) {
+ constructAndRenderComponentByID: function(constructor, props, id) {
+ var domNode = document.getElementById(id);
invariant(
- container && (
- container.nodeType === ELEMENT_NODE_TYPE ||
- container.nodeType === DOC_NODE_TYPE
- ),
- '_registerComponent(...): Target container is not a DOM element.'
+ domNode,
+ 'Tried to get element with id of "%s" but it is not present on the page.',
+ id
);
-
- ReactEventEmitter.ensureScrollValueMonitoring();
-
- var reactRootID = ReactMount.registerContainer(container);
- instancesByReactRootID[reactRootID] = nextComponent;
- return reactRootID;
+ return ReactMount.constructAndRenderComponent(constructor, props, domNode);
},
/**
@@ -311,21 +87,23 @@ var ReactMount = {
);
var componentInstance = instantiateReactComponent(nextComponent);
- var reactRootID = ReactMount._registerComponent(
+ var containerHandle = ReactDOMNodeMapping.getHandleForContainer(container);
+ var reactRootID = ReactDOMNodeHandleMapping.registerComponent(
componentInstance,
- container
+ containerHandle,
+ ReactDOMNodeMapping.getReactRootID(container)
+ );
+ ReactDOMNodeMapping.registerComponentInContainer(
+ reactRootID,
+ containerHandle
);
componentInstance.mountComponentIntoNode(
reactRootID,
- container,
+ ReactDOMNodeMapping.getHandleForContainer(container),
shouldReuseMarkup
);
- if (__DEV__) {
- // Record the root element in case it later gets transplanted.
- rootElementsByReactRootID[reactRootID] =
- getReactRootElementInContainer(container);
- }
+ ReactDOMNodeMapping.recordRootElementForTransplantWarning(container);
return componentInstance;
}
@@ -344,7 +122,7 @@ var ReactMount = {
* @return {ReactComponent} Component instance rendered in `container`.
*/
renderComponent: function(nextDescriptor, container, callback) {
- var prevComponent = instancesByReactRootID[getReactRootID(container)];
+ var prevComponent = ReactDOMNodeMapping.getInstanceFromContainer(container);
if (prevComponent) {
var prevDescriptor = prevComponent._descriptor;
@@ -360,9 +138,9 @@ var ReactMount = {
}
}
- var reactRootElement = getReactRootElementInContainer(container);
+ var reactRootElement = ReactDOMNodeMapping.getReactRootElementInContainer(container);
var containerHasReactMarkup =
- reactRootElement && ReactMount.isRenderedByReact(reactRootElement);
+ reactRootElement && ReactDOMNodeMapping.isRenderedByReact(reactRootElement);
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;
@@ -375,68 +153,22 @@ var ReactMount = {
return component;
},
- /**
- * Constructs a component instance of `constructor` with `initialProps` and
- * renders it into the supplied `container`.
- *
- * @param {function} constructor React component constructor.
- * @param {?object} props Initial props of the component instance.
- * @param {DOMElement} container DOM element to render into.
- * @return {ReactComponent} Component instance rendered in `container`.
- */
- constructAndRenderComponent: function(constructor, props, container) {
- return ReactMount.renderComponent(constructor(props), container);
- },
+ _updateRootComponent: function(
+ prevComponent,
+ nextComponent,
+ container,
+ callback) {
+ var nextProps = nextComponent.props;
+ ReactMount.scrollMonitor(container, function() {
+ prevComponent.replaceProps(nextProps, callback);
+ });
- /**
- * Constructs a component instance of `constructor` with `initialProps` and
- * renders it into a container node identified by supplied `id`.
- *
- * @param {function} componentConstructor React component constructor
- * @param {?object} props Initial props of the component instance.
- * @param {string} id ID of the DOM element to render into.
- * @return {ReactComponent} Component instance rendered in the container node.
- */
- constructAndRenderComponentByID: function(constructor, props, id) {
- var domNode = document.getElementById(id);
- invariant(
- domNode,
- 'Tried to get element with id of "%s" but it is not present on the page.',
- id
- );
- return ReactMount.constructAndRenderComponent(constructor, props, domNode);
- },
+ ReactDOMNodeMapping.recordRootElementForTransplantWarning(container);
- /**
- * Registers a container node into which React components will be rendered.
- * This also creates the "reactRoot" ID that will be assigned to the element
- * rendered within.
- *
- * @param {DOMElement} container DOM element to register as a container.
- * @return {string} The "reactRoot" ID of elements rendered within.
- */
- registerContainer: function(container) {
- var reactRootID = getReactRootID(container);
- if (reactRootID) {
- // If one exists, make sure it is a valid "reactRoot" ID.
- reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);
- }
- if (!reactRootID) {
- // No valid "reactRoot" ID found, create one.
- reactRootID = ReactInstanceHandles.createReactRootID();
- }
- containersByReactRootID[reactRootID] = container;
- return reactRootID;
+ return prevComponent;
},
- /**
- * Unmounts and destroys the React component rendered in the `container`.
- *
- * @param {DOMElement} container DOM element containing a React component.
- * @return {boolean} True if a component was found in and unmounted from
- * `container`
- */
- unmountComponentAtNode: function(container) {
+ unmountComponentAtNode: function() {
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (Strictly speaking, unmounting won't cause a
@@ -449,221 +181,20 @@ var ReactMount = {
'componentDidUpdate.'
);
- var reactRootID = getReactRootID(container);
- var component = instancesByReactRootID[reactRootID];
- if (!component) {
- return false;
- }
- ReactMount.unmountComponentFromNode(component, container);
- delete instancesByReactRootID[reactRootID];
- delete containersByReactRootID[reactRootID];
- if (__DEV__) {
- delete rootElementsByReactRootID[reactRootID];
- }
- return true;
+ return ReactDOMNodeMapping.unmountComponentAtNode.apply(ReactDOMNodeMapping, arguments);
},
/**
- * Unmounts a component and removes it from the DOM.
- *
- * @param {ReactComponent} instance React component instance.
- * @param {DOMElement} container DOM element to unmount from.
- * @final
- * @internal
- * @see {ReactMount.unmountComponentAtNode}
- */
- unmountComponentFromNode: function(instance, container) {
- instance.unmountComponent();
-
- if (container.nodeType === DOC_NODE_TYPE) {
- container = container.documentElement;
- }
-
- // http://jsperf.com/emptying-a-node
- while (container.lastChild) {
- container.removeChild(container.lastChild);
- }
- },
-
- /**
- * Finds the container DOM element that contains React component to which the
- * supplied DOM `id` belongs.
- *
- * @param {string} id The ID of an element rendered by a React component.
- * @return {?DOMElement} DOM element that contains the `id`.
- */
- findReactContainerForID: function(id) {
- var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
- var container = containersByReactRootID[reactRootID];
-
- if (__DEV__) {
- var rootElement = rootElementsByReactRootID[reactRootID];
- if (rootElement && rootElement.parentNode !== container) {
- invariant(
- // Call internalGetID here because getID calls isValid which calls
- // findReactContainerForID (this function).
- internalGetID(rootElement) === reactRootID,
- 'ReactMount: Root element ID differed from reactRootID.'
- );
-
- var containerChild = container.firstChild;
- if (containerChild &&
- reactRootID === internalGetID(containerChild)) {
- // If the container has a new child with the same ID as the old
- // root element, then rootElementsByReactRootID[reactRootID] is
- // just stale and needs to be updated. The case that deserves a
- // warning is when the container is empty.
- rootElementsByReactRootID[reactRootID] = containerChild;
- } else {
- console.warn(
- 'ReactMount: Root element has been removed from its original ' +
- 'container. New container:', rootElement.parentNode
- );
- }
- }
- }
-
- return container;
- },
-
- /**
- * Finds an element rendered by React with the supplied ID.
- *
- * @param {string} id ID of a DOM node in the React component.
- * @return {DOMElement} Root DOM node of the React component.
- */
- findReactNodeByID: function(id) {
- var reactRoot = ReactMount.findReactContainerForID(id);
- return ReactMount.findComponentRoot(reactRoot, id);
- },
-
- /**
- * True if the supplied `node` is rendered by React.
- *
- * @param {*} node DOM Element to check.
- * @return {boolean} True if the DOM Element appears to be rendered by React.
- * @internal
- */
- isRenderedByReact: function(node) {
- if (node.nodeType !== 1) {
- // Not a DOMElement, therefore not a React component
- return false;
- }
- var id = ReactMount.getID(node);
- return id ? id.charAt(0) === SEPARATOR : false;
- },
-
- /**
- * Traverses up the ancestors of the supplied node to find a node that is a
- * DOM representation of a React component.
- *
- * @param {*} node
- * @return {?DOMEventTarget}
- * @internal
- */
- getFirstReactDOM: function(node) {
- var current = node;
- while (current && current.parentNode !== current) {
- if (ReactMount.isRenderedByReact(current)) {
- return current;
- }
- current = current.parentNode;
- }
- return null;
- },
-
- /**
- * Finds a node with the supplied `targetID` inside of the supplied
- * `ancestorNode`. Exploits the ID naming scheme to perform the search
- * quickly.
+ * This is a hook provided to support rendering React components while
+ * ensuring that the apparent scroll position of its `container` does not
+ * change.
*
- * @param {DOMEventTarget} ancestorNode Search from this root.
- * @pararm {string} targetID ID of the DOM representation of the component.
- * @return {DOMEventTarget} DOM node with the supplied `targetID`.
- * @internal
- */
- findComponentRoot: function(ancestorNode, targetID) {
- var firstChildren = findComponentRootReusableArray;
- var childIndex = 0;
-
- var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode;
-
- firstChildren[0] = deepestAncestor.firstChild;
- firstChildren.length = 1;
-
- while (childIndex < firstChildren.length) {
- var child = firstChildren[childIndex++];
- var targetChild;
-
- while (child) {
- var childID = ReactMount.getID(child);
- if (childID) {
- // Even if we find the node we're looking for, we finish looping
- // through its siblings to ensure they're cached so that we don't have
- // to revisit this node again. Otherwise, we make n^2 calls to getID
- // when visiting the many children of a single node in order.
-
- if (targetID === childID) {
- targetChild = child;
- } else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) {
- // If we find a child whose ID is an ancestor of the given ID,
- // then we can be sure that we only want to search the subtree
- // rooted at this child, so we can throw out the rest of the
- // search state.
- firstChildren.length = childIndex = 0;
- firstChildren.push(child.firstChild);
- }
-
- } else {
- // If this child had no ID, then there's a chance that it was
- // injected automatically by the browser, as when a `
`
- // element sprouts an extra `` child as a side effect of
- // `.innerHTML` parsing. Optimistically continue down this
- // branch, but not before examining the other siblings.
- firstChildren.push(child.firstChild);
- }
-
- child = child.nextSibling;
- }
-
- if (targetChild) {
- // Emptying firstChildren/findComponentRootReusableArray is
- // not necessary for correctness, but it helps the GC reclaim
- // any nodes that were left at the end of the search.
- firstChildren.length = 0;
-
- return targetChild;
- }
- }
-
- firstChildren.length = 0;
-
- invariant(
- false,
- 'findComponentRoot(..., %s): Unable to find element. This probably ' +
- 'means the DOM was unexpectedly mutated (e.g., by the browser), ' +
- 'usually due to forgetting a when using tables or nesting ' +
- 'or tags. Try inspecting the child nodes of the element with React ' +
- 'ID `%s`.',
- targetID,
- ReactMount.getID(ancestorNode)
- );
- },
-
-
- /**
- * React ID utilities.
+ * @param {DOMElement} container The `container` being rendered into.
+ * @param {function} renderCallback This must be called once to do the render.
*/
-
- getReactRootID: getReactRootID,
-
- getID: getID,
-
- setID: setID,
-
- getNode: getNode,
-
- purgeID: purgeID
+ scrollMonitor: function(container, renderCallback) {
+ renderCallback();
+ }
};
module.exports = ReactMount;
diff --git a/src/browser/ReactReconcileTransaction.js b/src/browser/ui/ReactReconcileTransaction.js
similarity index 100%
rename from src/browser/ReactReconcileTransaction.js
rename to src/browser/ui/ReactReconcileTransaction.js
diff --git a/src/browser/ui/ReactWorker.js b/src/browser/ui/ReactWorker.js
new file mode 100644
index 0000000000000..e29cde105c22d
--- /dev/null
+++ b/src/browser/ui/ReactWorker.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactWorker
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var ReactComponentBrowserEnvironment =
+ require('ReactComponentBrowserEnvironment');
+var ReactDOMIDOperations = require('ReactDOMIDOperations');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
+var ReactEventListener = require('ReactEventListener');
+var RemoteModule = require('RemoteModule');
+var RemoteModuleServer = require('RemoteModuleServer');
+
+var keyOf = require('keyOf');
+
+// The UI thread uses this to kick off the worker.
+class ReactWorker {
+ constructor(scriptURI) {
+ this.worker = new Worker(scriptURI);
+ this.server = new RemoteModuleServer(this.worker, {
+ ReactComponentBrowserEnvironment: ReactComponentBrowserEnvironment,
+ ReactDOMIDOperations: ReactDOMIDOperations,
+ ReactDOMNodeMapping: ReactDOMNodeMapping,
+ ReactEventListener: ReactEventListener
+ });
+
+ var ReactEventEmitterRemote = new RemoteModule(
+ this.worker,
+ keyOf({ReactEventEmitter: null}),
+ {handleTopLevel: null}
+ );
+
+ ReactEventListener.setHandleTopLevel(
+ function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
+ ReactEventEmitterRemote.handleTopLevel(
+ topLevelType,
+ {},
+ topLevelTargetID,
+ {target: {}}
+ );
+ }
+ );
+ }
+
+ terminate() {
+ this.server.destroy();
+ this.worker.terminate();
+ }
+}
+
+ReactWorker.run = function(script, dependencies, main) {
+ if (ExecutionEnvironment.canUseDOM) {
+ return new ReactWorker(script);
+ } else {
+ if (dependencies.length > 0) {
+ importScripts.apply(null, dependencies);
+ }
+ main();
+ return self;
+ }
+};
+
+module.exports = ReactWorker;
diff --git a/src/browser/ui/__tests__/ReactDOMComponent-test.js b/src/browser/ui/__tests__/ReactDOMComponent-test.js
index ccc279f2e29e4..78d42b176ee89 100644
--- a/src/browser/ui/__tests__/ReactDOMComponent-test.js
+++ b/src/browser/ui/__tests__/ReactDOMComponent-test.js
@@ -384,7 +384,7 @@ describe('ReactDOMComponent', function() {
it("should clean up listeners", function() {
var React = require('React');
var ReactEventEmitter = require('ReactEventEmitter');
- var ReactMount = require('ReactMount');
+ var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var container = document.createElement('div');
document.documentElement.appendChild(container);
@@ -394,7 +394,7 @@ describe('ReactDOMComponent', function() {
instance = React.renderComponent(instance, container);
var rootNode = instance.getDOMNode();
- var rootNodeID = ReactMount.getID(rootNode);
+ var rootNodeID = ReactDOMNodeMapping.getID(rootNode);
expect(
ReactEventEmitter.getListener(rootNodeID, 'onClick')
).toBe(callback);
diff --git a/src/browser/ui/__tests__/ReactDOMIDOperations-test.js b/src/browser/ui/__tests__/ReactDOMIDOperations-test.js
index a1b93f04ec3aa..e21798598518f 100644
--- a/src/browser/ui/__tests__/ReactDOMIDOperations-test.js
+++ b/src/browser/ui/__tests__/ReactDOMIDOperations-test.js
@@ -23,11 +23,11 @@
describe('ReactDOMIDOperations', function() {
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
- var ReactMount = require('ReactMount');
+ var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var keyOf = require('keyOf');
it('should disallow updating special properties', function() {
- spyOn(ReactMount, "getNode");
+ spyOn(ReactDOMNodeMapping, "getNode");
spyOn(DOMPropertyOperations, "setValueForProperty");
expect(function() {
@@ -39,7 +39,7 @@ describe('ReactDOMIDOperations', function() {
}).toThrow();
expect(
- ReactMount.getNode.argsForCall[0][0]
+ ReactDOMNodeMapping.getNode.argsForCall[0][0]
).toBe('testID');
expect(
@@ -49,17 +49,17 @@ describe('ReactDOMIDOperations', function() {
it('should update innerHTML and preserve whitespace', function() {
var stubNode = document.createElement('div');
- spyOn(ReactMount, "getNode").andReturn(stubNode);
+ spyOn(ReactDOMNodeMapping, "getNode").andReturn(stubNode);
var html = '\n \t \n testContent \t \n \t';
- ReactDOMIDOperations.updateInnerHTMLByID(
+ ReactDOMIDOperations.updateImageByID(
'testID',
html
);
expect(
- ReactMount.getNode.argsForCall[0][0]
+ ReactDOMNodeMapping.getNode.argsForCall[0][0]
).toBe('testID');
expect(stubNode.innerHTML).toBe(html);
diff --git a/src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js b/src/browser/ui/__tests__/ReactEventListener-test.js
similarity index 83%
rename from src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js
rename to src/browser/ui/__tests__/ReactEventListener-test.js
index d077a670628d5..d6dd91a8a9a99 100644
--- a/src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js
+++ b/src/browser/ui/__tests__/ReactEventListener-test.js
@@ -19,23 +19,24 @@
'use strict';
-require('mock-modules')
- .mock('ReactEventEmitter');
+var mocks = require('mocks');
var EVENT_TARGET_PARAM = 1;
-describe('ReactEventTopLevelCallback', function() {
+describe('ReactEventListener', function() {
var React;
- var ReactEventTopLevelCallback;
var ReactMount;
- var ReactEventEmitter; // mocked
+ var ReactEventListener;
+ var handleTopLevel;
beforeEach(function() {
require('mock-modules').dumpCache();
React = require('React');
- ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
ReactMount = require('ReactMount');
- ReactEventEmitter = require('ReactEventEmitter'); // mocked
+ ReactEventListener = require('ReactEventListener');
+
+ handleTopLevel = mocks.getMockFunction();
+ ReactEventListener._handleTopLevel = handleTopLevel;
});
describe('Propagation', function() {
@@ -48,12 +49,12 @@ describe('ReactEventTopLevelCallback', function() {
parentControl = ReactMount.renderComponent(parentControl, parentContainer);
parentControl.getDOMNode().appendChild(childContainer);
- var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
+ var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
callback({
target: childControl.getDOMNode()
});
- var calls = ReactEventEmitter.handleTopLevel.mock.calls;
+ var calls = handleTopLevel.mock.calls;
expect(calls.length).toBe(2);
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childControl.getDOMNode());
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
@@ -72,12 +73,12 @@ describe('ReactEventTopLevelCallback', function() {
parentControl.getDOMNode().appendChild(childContainer);
grandParentControl.getDOMNode().appendChild(parentContainer);
- var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
+ var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
callback({
target: childControl.getDOMNode()
});
- var calls = ReactEventEmitter.handleTopLevel.mock.calls;
+ var calls = handleTopLevel.mock.calls;
expect(calls.length).toBe(3);
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childControl.getDOMNode());
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
@@ -99,7 +100,7 @@ describe('ReactEventTopLevelCallback', function() {
// handlers are called; we'll still expect to receive a second call for
// the parent control.
var childNode = childControl.getDOMNode();
- ReactEventEmitter.handleTopLevel.mockImplementation(
+ handleTopLevel.mockImplementation(
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
if (topLevelTarget === childNode) {
ReactMount.unmountComponentAtNode(childContainer);
@@ -107,12 +108,12 @@ describe('ReactEventTopLevelCallback', function() {
}
);
- var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
+ var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
callback({
target: childNode
});
- var calls = ReactEventEmitter.handleTopLevel.mock.calls;
+ var calls = handleTopLevel.mock.calls;
expect(calls.length).toBe(2);
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childNode);
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
@@ -134,7 +135,7 @@ describe('ReactEventTopLevelCallback', function() {
// Suppose an event handler in each root enqueues an update to the
// childControl element -- the two updates should get batched together.
var childNode = childControl.getDOMNode();
- ReactEventEmitter.handleTopLevel.mockImplementation(
+ handleTopLevel.mockImplementation(
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
ReactMount.renderComponent(
{topLevelTarget === childNode ? '1' : '2'}
,
@@ -145,12 +146,12 @@ describe('ReactEventTopLevelCallback', function() {
}
);
- var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
+ var callback = ReactEventListener.dispatchEvent.bind(ReactEventListener, 'test');
callback({
target: childNode
});
- var calls = ReactEventEmitter.handleTopLevel.mock.calls;
+ var calls = handleTopLevel.mock.calls;
expect(calls.length).toBe(2);
expect(childNode.textContent).toBe('2');
});
@@ -173,12 +174,12 @@ describe('ReactEventTopLevelCallback', function() {
var instance = ReactMount.renderComponent( , container);
- var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
+ var callback = ReactEventListener.dispatchEvent.bind(null, 'test');
callback({
target: instance.getInner().getDOMNode()
});
- var calls = ReactEventEmitter.handleTopLevel.mock.calls;
+ var calls = handleTopLevel.mock.calls;
expect(calls.length).toBe(1);
expect(calls[0][EVENT_TARGET_PARAM]).toBe(instance.getInner().getDOMNode());
});
diff --git a/src/browser/ui/__tests__/ReactMountDestruction-test.js b/src/browser/ui/__tests__/ReactMountDestruction-test.js
index c73ddb461f611..c2796c313a569 100644
--- a/src/browser/ui/__tests__/ReactMountDestruction-test.js
+++ b/src/browser/ui/__tests__/ReactMountDestruction-test.js
@@ -21,7 +21,7 @@
var React = require('React');
-describe('ReactMount', function() {
+describe('ReactDOMNodeMapping', function() {
it("should destroy a react root upon request", function() {
var mainContainerDiv = document.createElement('div');
document.documentElement.appendChild(mainContainerDiv);
diff --git a/src/browser/ui/__tests__/ReactRenderDocument-test.js b/src/browser/ui/__tests__/ReactRenderDocument-test.js
index 8a545252eded6..bf9e8202afec7 100644
--- a/src/browser/ui/__tests__/ReactRenderDocument-test.js
+++ b/src/browser/ui/__tests__/ReactRenderDocument-test.js
@@ -22,7 +22,7 @@
"use strict";
var React;
-var ReactMount;
+var ReactDOMNodeMapping;
var getTestDocument;
@@ -40,7 +40,7 @@ describe('rendering React components at document', function() {
require('mock-modules').dumpCache();
React = require('React');
- ReactMount = require('ReactMount');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
getTestDocument = require('getTestDocument');
testDocument = getTestDocument();
@@ -69,7 +69,7 @@ describe('rendering React components at document', function() {
var component = React.renderComponent( , testDocument);
expect(testDocument.body.innerHTML).toBe('Hello world');
- var componentID = ReactMount.getReactRootID(testDocument);
+ var componentID = ReactDOMNodeMapping.getReactRootID(testDocument);
expect(componentID).toBe(component._rootNodeID);
});
diff --git a/src/browser/ui/dom/DOMChildrenOperations.js b/src/browser/ui/dom/DOMChildrenOperations.js
index e2e3de2005a7d..0e602dcf27d2d 100644
--- a/src/browser/ui/dom/DOMChildrenOperations.js
+++ b/src/browser/ui/dom/DOMChildrenOperations.js
@@ -145,10 +145,10 @@ var DOMChildrenOperations = {
for (var k = 0; update = updates[k]; k++) {
switch (update.type) {
- case ReactMultiChildUpdateTypes.INSERT_MARKUP:
+ case ReactMultiChildUpdateTypes.INSERT_IMAGE:
insertChildAt(
update.parentNode,
- renderedMarkup[update.markupIndex],
+ renderedMarkup[update.imageIndex],
update.toIndex
);
break;
diff --git a/src/browser/ui/dom/ViewportMetrics.js b/src/browser/ui/dom/ViewportMetrics.js
index 621a517220bf6..375917c289288 100644
--- a/src/browser/ui/dom/ViewportMetrics.js
+++ b/src/browser/ui/dom/ViewportMetrics.js
@@ -18,16 +18,13 @@
"use strict";
-var getUnboundedScrollPosition = require('getUnboundedScrollPosition');
-
var ViewportMetrics = {
currentScrollLeft: 0,
currentScrollTop: 0,
- refreshScrollValues: function() {
- var scrollPosition = getUnboundedScrollPosition(window);
+ refreshScrollValues: function(scrollPosition) {
ViewportMetrics.currentScrollLeft = scrollPosition.x;
ViewportMetrics.currentScrollTop = scrollPosition.y;
}
diff --git a/src/browser/ui/dom/components/ReactDOMForm.js b/src/browser/ui/dom/components/ReactDOMForm.js
index b8696be86085b..45429fb2344fd 100644
--- a/src/browser/ui/dom/components/ReactDOMForm.js
+++ b/src/browser/ui/dom/components/ReactDOMForm.js
@@ -21,6 +21,7 @@
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
var ReactEventEmitter = require('ReactEventEmitter');
var EventConstants = require('EventConstants');
@@ -49,12 +50,12 @@ var ReactDOMForm = ReactCompositeComponent.createClass({
ReactEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topReset,
'reset',
- this.getDOMNode()
+ ReactDOMNodeHandle.getHandleForReactID(this._rootNodeID)
);
ReactEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topSubmit,
'submit',
- this.getDOMNode()
+ ReactDOMNodeHandle.getHandleForReactID(this._rootNodeID)
);
}
});
diff --git a/src/browser/ui/dom/components/ReactDOMImg.js b/src/browser/ui/dom/components/ReactDOMImg.js
index 46b154f69d6dd..663196614f603 100644
--- a/src/browser/ui/dom/components/ReactDOMImg.js
+++ b/src/browser/ui/dom/components/ReactDOMImg.js
@@ -21,6 +21,7 @@
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
var ReactEventEmitter = require('ReactEventEmitter');
var EventConstants = require('EventConstants');
@@ -48,12 +49,12 @@ var ReactDOMImg = ReactCompositeComponent.createClass({
ReactEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topLoad,
'load',
- node
+ ReactDOMNodeHandle.getHandleForReactID(this._rootNodeID)
);
ReactEventEmitter.trapBubbledEvent(
EventConstants.topLevelTypes.topError,
'error',
- node
+ ReactDOMNodeHandle.getHandleForReactID(this._rootNodeID)
);
}
});
diff --git a/src/browser/ui/dom/components/ReactDOMInput.js b/src/browser/ui/dom/components/ReactDOMInput.js
index 0437f60513c07..8ffec61ed7c9f 100644
--- a/src/browser/ui/dom/components/ReactDOMInput.js
+++ b/src/browser/ui/dom/components/ReactDOMInput.js
@@ -24,7 +24,7 @@ var LinkedValueUtils = require('LinkedValueUtils');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactDOM = require('ReactDOM');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var invariant = require('invariant');
var merge = require('merge');
@@ -87,13 +87,13 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
},
componentDidMount: function() {
- var id = ReactMount.getID(this.getDOMNode());
+ var id = ReactDOMNodeMapping.getID(this.getDOMNode());
instancesByReactID[id] = this;
},
componentWillUnmount: function() {
var rootNode = this.getDOMNode();
- var id = ReactMount.getID(rootNode);
+ var id = ReactDOMNodeMapping.getID(rootNode);
delete instancesByReactID[id];
},
@@ -152,7 +152,7 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
otherNode.form !== rootNode.form) {
continue;
}
- var otherID = ReactMount.getID(otherNode);
+ var otherID = ReactDOMNodeMapping.getID(otherNode);
invariant(
otherID,
'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
diff --git a/src/browser/ui/getReactRootElementInContainer.js b/src/browser/worker/ReactComponentBrowserEnvironmentRemote.js
similarity index 52%
rename from src/browser/ui/getReactRootElementInContainer.js
rename to src/browser/worker/ReactComponentBrowserEnvironmentRemote.js
index 3ecf18194dcad..10c0c228402a3 100644
--- a/src/browser/ui/getReactRootElementInContainer.js
+++ b/src/browser/worker/ReactComponentBrowserEnvironmentRemote.js
@@ -13,28 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
- * @providesModule getReactRootElementInContainer
+ * @providesModule ReactComponentBrowserEnvironmentRemote
*/
"use strict";
-var DOC_NODE_TYPE = 9;
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var RemoteModule = require('RemoteModule');
-/**
- * @param {DOMElement|DOMDocument} container DOM element that may contain
- * a React component
- * @return {?*} DOM element that may have the reactRoot ID, or null.
- */
-function getReactRootElementInContainer(container) {
- if (!container) {
- return null;
- }
+var keyOf = require('keyOf');
- if (container.nodeType === DOC_NODE_TYPE) {
- return container.documentElement;
- } else {
- return container.firstChild;
+var ReactComponentBrowserEnvironmentRemote = new RemoteModule(
+ ExecutionEnvironment.global,
+ keyOf({ReactComponentBrowserEnvironment: null}),
+ {
+ mountImageIntoNode: null,
+ unmountIDFromEnvironment: null
}
-}
+);
-module.exports = getReactRootElementInContainer;
+module.exports = ReactComponentBrowserEnvironmentRemote;
diff --git a/src/browser/worker/ReactComponentWorkerEnvironment.js b/src/browser/worker/ReactComponentWorkerEnvironment.js
new file mode 100644
index 0000000000000..51a2591376892
--- /dev/null
+++ b/src/browser/worker/ReactComponentWorkerEnvironment.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactComponentWorkerEnvironment
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var ReactComponentBrowserEnvironmentRemote =
+ require('ReactComponentBrowserEnvironmentRemote');
+var ReactDOMIDOperationsRemote = require('ReactDOMIDOperationsRemote');
+var ReactWorkerReconcileTransaction = require('ReactWorkerReconcileTransaction');
+
+var ReactComponentWorkerEnvironment = {
+ ReactReconcileTransaction: ReactWorkerReconcileTransaction,
+
+ BackendIDOperations: ReactDOMIDOperationsRemote,
+
+ unmountIDFromEnvironment:
+ ReactComponentBrowserEnvironmentRemote.unmountIDFromEnvironment,
+
+ mountImageIntoNode:
+ ReactComponentBrowserEnvironmentRemote.mountImageIntoNode,
+};
+
+module.exports = ReactComponentWorkerEnvironment;
diff --git a/src/browser/worker/ReactDOMIDOperationsRemote.js b/src/browser/worker/ReactDOMIDOperationsRemote.js
new file mode 100644
index 0000000000000..16b075c5ccb15
--- /dev/null
+++ b/src/browser/worker/ReactDOMIDOperationsRemote.js
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMIDOperationsRemote
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var RemoteModule = require('RemoteModule');
+
+var keyOf = require('keyOf');
+
+var ReactDOMIDOperationsRemote = new RemoteModule(
+ ExecutionEnvironment.global,
+ keyOf({ReactDOMIDOperations: null}),
+ {
+ updatePropertyByID: null,
+ deletePropertyByID: null,
+ updateStylesByID: null,
+ updateImageByID: null,
+ updateTextContentByID: null,
+ dangerouslyReplaceNodeWithMarkupByID: null,
+ dangerouslyProcessChildrenUpdates: null
+ }
+);
+
+module.exports = ReactDOMIDOperationsRemote;
diff --git a/src/browser/worker/ReactDOMNodeMappingRemote.js b/src/browser/worker/ReactDOMNodeMappingRemote.js
new file mode 100644
index 0000000000000..823cee57125e8
--- /dev/null
+++ b/src/browser/worker/ReactDOMNodeMappingRemote.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactDOMNodeMappingRemote
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var RemoteModule = require('RemoteModule');
+
+var keyOf = require('keyOf');
+
+var ReactDOMNodeMappingRemote = new RemoteModule(
+ ExecutionEnvironment.global,
+ keyOf({ReactDOMNodeMapping: null}),
+ // TODO: we should codegen this when we move to a better bridge
+ // so we can get type checking.
+ // TODO: should we move this out of ReactDOMNodeMapping?
+ {
+ registerContainerHandle: null,
+ unmountComponentAtHandle: null,
+ registerComponentInContainer: null
+ }
+);
+
+module.exports = ReactDOMNodeMappingRemote;
diff --git a/src/browser/worker/ReactEventListenerRemote.js b/src/browser/worker/ReactEventListenerRemote.js
new file mode 100644
index 0000000000000..fcab5ddae423d
--- /dev/null
+++ b/src/browser/worker/ReactEventListenerRemote.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactEventListenerRemote
+ */
+
+"use strict";
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+var RemoteModule = require('RemoteModule');
+
+var copyProperties = require('copyProperties');
+var emptyFunction = require('emptyFunction');
+var keyOf = require('keyOf');
+
+var enabled = true;
+
+var ReactEventListenerRemote = new RemoteModule(
+ ExecutionEnvironment.global,
+ keyOf({ReactEventListener: null}),
+ {
+ monitorScrollValue: null,
+ setEnabled: null,
+ trapBubbledEvent: null,
+ trapCapturedEvent: null
+ }
+);
+
+var _setEnabled = ReactEventListenerRemote.setEnabled;
+
+copyProperties(ReactEventListenerRemote, {
+ setEnabled: function(value) {
+ enabled = value;
+ _setEnabled(value);
+ },
+
+ isEnabled: function() {
+ return enabled;
+ },
+
+ // This is handled by RemoteModuleServer
+ setHandleTopLevel: emptyFunction
+});
+
+module.exports = ReactEventListenerRemote;
diff --git a/src/browser/worker/ReactWorkerInjection.js b/src/browser/worker/ReactWorkerInjection.js
new file mode 100644
index 0000000000000..4b974a628d882
--- /dev/null
+++ b/src/browser/worker/ReactWorkerInjection.js
@@ -0,0 +1,144 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactWorkerInjection
+ */
+
+"use strict";
+
+var DOMProperty = require('DOMProperty');
+var EventPluginHub = require('EventPluginHub');
+var ReactComponent = require('ReactComponent');
+var ReactCompositeComponent = require('ReactCompositeComponent');
+var ReactDOM = require('ReactDOM');
+var ReactEmptyComponent = require('ReactEmptyComponent');
+var ReactEventEmitter = require('ReactEventEmitter');
+var ReactPerf = require('ReactPerf');
+var ReactRootIndex = require('ReactRootIndex');
+var ReactUpdates = require('ReactUpdates');
+
+var ExecutionEnvironment = require('ExecutionEnvironment');
+
+var ChangeEventPlugin = require('ChangeEventPlugin');
+var ClientReactRootIndex = require('ClientReactRootIndex');
+var CompositionEventPlugin = require('CompositionEventPlugin');
+var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
+var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
+var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig');
+var MobileSafariClickEventPlugin = require('MobileSafariClickEventPlugin');
+var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
+var ReactComponentWorkerEnvironment =
+ require('ReactComponentWorkerEnvironment');
+var ReactEventListenerRemote = require('ReactEventListenerRemote');
+var ReactDOM = require('ReactDOM');
+var ReactDOMButton = require('ReactDOMButton');
+var ReactDOMForm = require('ReactDOMForm');
+var ReactDOMImg = require('ReactDOMImg');
+var ReactDOMInput = require('ReactDOMInput');
+var ReactDOMOption = require('ReactDOMOption');
+var ReactDOMSelect = require('ReactDOMSelect');
+var ReactDOMTextarea = require('ReactDOMTextarea');
+var ReactEventEmitter = require('ReactEventEmitter');
+var ReactInstanceHandles = require('ReactInstanceHandles');
+var SelectEventPlugin = require('SelectEventPlugin');
+var SimpleEventPlugin = require('SimpleEventPlugin');
+var SVGDOMPropertyConfig = require('SVGDOMPropertyConfig');
+var BeforeInputEventPlugin = require('BeforeInputEventPlugin');
+
+var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
+var RemoteModuleServer = require('RemoteModuleServer');
+
+var createFullPageComponent = require('createFullPageComponent');
+
+var server;
+
+function inject() {
+ server = new RemoteModuleServer(ExecutionEnvironment.global, {
+ ReactEventEmitter: ReactEventEmitter
+ });
+
+ ReactEventEmitter.injection.injectReactEventListener(
+ ReactEventListenerRemote
+ );
+
+ /**
+ * Inject modules for resolving DOM hierarchy and plugin ordering.
+ */
+ EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
+ EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
+ EventPluginHub.injection.injectMount({getNode: function() {}});
+
+ /**
+ * Some important event plugins included by default (without having to require
+ * them).
+ */
+
+ EventPluginHub.injection.injectEventPluginsByName({
+ SimpleEventPlugin: SimpleEventPlugin,
+ EnterLeaveEventPlugin: EnterLeaveEventPlugin,
+ ChangeEventPlugin: ChangeEventPlugin,
+ CompositionEventPlugin: CompositionEventPlugin,
+ MobileSafariClickEventPlugin: MobileSafariClickEventPlugin,
+ SelectEventPlugin: SelectEventPlugin,
+ BeforeInputEventPlugin: BeforeInputEventPlugin
+ });
+
+ ReactDOM.injection.injectComponentClasses({
+ button: ReactDOMButton,
+ form: ReactDOMForm,
+ img: ReactDOMImg,
+ input: ReactDOMInput,
+ option: ReactDOMOption,
+ select: ReactDOMSelect,
+ textarea: ReactDOMTextarea,
+
+ html: createFullPageComponent(ReactDOM.html),
+ head: createFullPageComponent(ReactDOM.head),
+ title: createFullPageComponent(ReactDOM.title),
+ body: createFullPageComponent(ReactDOM.body)
+ });
+
+
+ // This needs to happen after createFullPageComponent() otherwise the mixin
+ // gets double injected.
+ ReactCompositeComponent.injection.injectMixin(ReactBrowserComponentMixin);
+
+ DOMProperty.injection.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
+ DOMProperty.injection.injectDOMPropertyConfig(SVGDOMPropertyConfig);
+
+ ReactEmptyComponent.injection.injectEmptyComponent(ReactDOM.script);
+
+ ReactUpdates.injection.injectBatchingStrategy(
+ ReactDefaultBatchingStrategy
+ );
+
+ ReactRootIndex.injection.injectCreateReactRootIndex(
+ ClientReactRootIndex.createReactRootIndex
+ );
+
+ ReactComponent.injection.injectEnvironment(ReactComponentWorkerEnvironment);
+
+ if (__DEV__) {
+ var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
+ if ((/[?&]react_perf\b/).test(url)) {
+ var ReactDefaultPerf = require('ReactDefaultPerf');
+ ReactDefaultPerf.start();
+ }
+ }
+}
+
+module.exports = {
+ inject: inject
+};
diff --git a/src/browser/worker/ReactWorkerMount.js b/src/browser/worker/ReactWorkerMount.js
new file mode 100644
index 0000000000000..545571b4008da
--- /dev/null
+++ b/src/browser/worker/ReactWorkerMount.js
@@ -0,0 +1,139 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactWorkerMount
+ */
+
+"use strict";
+
+var ReactDOMNodeHandle = require('ReactDOMNodeHandle');
+var ReactDOMNodeHandleMapping = require('ReactDOMNodeHandleMapping');
+var ReactDOMNodeMappingRemote = require('ReactDOMNodeMappingRemote');
+var ReactPerf = require('ReactPerf');
+
+var instantiateReactComponent = require('instantiateReactComponent');
+var invariant = require('invariant');
+var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
+
+var ReactWorkerMount = {
+ /**
+ * Render a new component into the DOM.
+ * @param {ReactComponent} nextComponent component instance to render
+ * @param {DOMElement} container container to render into
+ * @param {boolean} shouldReuseMarkup if we should skip the markup insertion
+ * @return {ReactComponent} nextComponent
+ */
+ _renderNewRootComponent: ReactPerf.measure(
+ 'ReactMount',
+ '_renderNewRootComponent',
+ function(
+ nextComponent,
+ containerHandle,
+ shouldReuseMarkup) {
+ var componentInstance = instantiateReactComponent(nextComponent);
+
+ ReactDOMNodeMappingRemote.registerContainerHandle(containerHandle);
+
+ var reactRootID = ReactDOMNodeHandleMapping.registerComponent(
+ componentInstance,
+ containerHandle
+ );
+
+ ReactDOMNodeMappingRemote.registerComponentInContainer(
+ reactRootID,
+ containerHandle
+ );
+
+ componentInstance.mountComponentIntoNode(
+ reactRootID,
+ containerHandle,
+ shouldReuseMarkup
+ );
+
+ return componentInstance;
+ }
+ ),
+
+ /**
+ * Renders a React component into the DOM in the supplied `container`.
+ *
+ * If the React component was previously rendered into `container`, this will
+ * perform an update on it and only mutate the DOM as necessary to reflect the
+ * latest React component.
+ *
+ * @param {ReactDescriptor} nextDescriptor Component descriptor to render.
+ * @param {DOMElement} container DOM element to render into.
+ * @param {?function} callback function triggered on completion
+ * @return {ReactComponent} Component instance rendered in `container`.
+ */
+ renderComponent: function(nextDescriptor, containerID, callback) {
+ var containerHandle = ReactDOMNodeHandle.getHandleForContainerID(
+ containerID
+ );
+
+ var prevComponent = ReactDOMNodeHandleMapping.getInstanceFromContainer(containerHandle);
+
+ if (prevComponent) {
+ var prevDescriptor = prevComponent._descriptor;
+ if (shouldUpdateReactComponent(prevDescriptor, nextDescriptor)) {
+ return ReactWorkerMount._updateRootComponent(
+ prevComponent,
+ nextDescriptor,
+ containerHandle,
+ callback
+ );
+ } else {
+ ReactWorkerMount.unmountComponentAtHandle(containerHandle);
+ }
+ }
+
+ var component = ReactWorkerMount._renderNewRootComponent(
+ nextDescriptor,
+ containerHandle,
+ false // TODO: figure out hwo to reuse markup from a worker
+ );
+ callback && callback.call(component);
+ return component;
+ },
+
+ _updateRootComponent: function(
+ prevComponent,
+ nextComponent,
+ containerHandle,
+ callback) {
+ var nextProps = nextComponent.props;
+ prevComponent.replaceProps(nextProps, callback);
+
+ return prevComponent;
+ },
+
+ unmountComponentAtHandle: function(handle) {
+ ReactDOMNodeMappingRemote.unmountComponentAtHandle(handle);
+ },
+
+ /**
+ * This is a hook provided to support rendering React components while
+ * ensuring that the apparent scroll position of its `container` does not
+ * change.
+ *
+ * @param {DOMElement} container The `container` being rendered into.
+ * @param {function} renderCallback This must be called once to do the render.
+ */
+ scrollMonitor: function(containerHandle, renderCallback) {
+ renderCallback();
+ }
+};
+
+module.exports = ReactWorkerMount;
diff --git a/src/browser/worker/ReactWorkerReconcileTransaction.js b/src/browser/worker/ReactWorkerReconcileTransaction.js
new file mode 100644
index 0000000000000..57d40b3f45752
--- /dev/null
+++ b/src/browser/worker/ReactWorkerReconcileTransaction.js
@@ -0,0 +1,164 @@
+/**
+ * Copyright 2013-2014 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @providesModule ReactWorkerReconcileTransaction
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var CallbackQueue = require('CallbackQueue');
+var PooledClass = require('PooledClass');
+var ReactEventEmitter = require('ReactEventEmitter');
+var ReactPutListenerQueue = require('ReactPutListenerQueue');
+var Transaction = require('Transaction');
+
+var mixInto = require('mixInto');
+
+/**
+ * Suppresses events (blur/focus) that could be inadvertently dispatched due to
+ * high level DOM manipulations (like temporarily removing a text input from the
+ * DOM).
+ */
+var EVENT_SUPPRESSION = {
+ /**
+ * @return {boolean} The enabled status of `ReactEventEmitter` before the
+ * reconciliation.
+ */
+ initialize: function() {
+ var currentlyEnabled = ReactEventEmitter.isEnabled();
+ ReactEventEmitter.setEnabled(false);
+ return currentlyEnabled;
+ },
+
+ /**
+ * @param {boolean} previouslyEnabled Enabled status of `ReactEventEmitter`
+ * before the reconciliation occured. `close` restores the previous value.
+ */
+ close: function(previouslyEnabled) {
+ ReactEventEmitter.setEnabled(previouslyEnabled);
+ }
+};
+
+/**
+ * Provides a queue for collecting `componentDidMount` and
+ * `componentDidUpdate` callbacks during the the transaction.
+ */
+var ON_DOM_READY_QUEUEING = {
+ /**
+ * Initializes the internal `onDOMReady` queue.
+ */
+ initialize: function() {
+ this.reactMountReady.reset();
+ },
+
+ /**
+ * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
+ */
+ close: function() {
+ this.reactMountReady.notifyAll();
+ }
+};
+
+var PUT_LISTENER_QUEUEING = {
+ initialize: function() {
+ this.putListenerQueue.reset();
+ },
+
+ close: function() {
+ this.putListenerQueue.putListeners();
+ }
+};
+
+/**
+ * Executed within the scope of the `Transaction` instance. Consider these as
+ * being member methods, but with an implied ordering while being isolated from
+ * each other.
+ */
+var TRANSACTION_WRAPPERS = [
+ PUT_LISTENER_QUEUEING,
+ EVENT_SUPPRESSION,
+ ON_DOM_READY_QUEUEING
+];
+
+/**
+ * Currently:
+ * - The order that these are listed in the transaction is critical:
+ * - Suppresses events.
+ * - Restores selection range.
+ *
+ * Future:
+ * - Restore document/overflow scroll positions that were unintentionally
+ * modified via DOM insertions above the top viewport boundary.
+ * - Implement/integrate with customized constraint based layout system and keep
+ * track of which dimensions must be remeasured.
+ *
+ * @class ReactWorkerReconcileTransaction
+ */
+function ReactWorkerReconcileTransaction() {
+ this.reinitializeTransaction();
+ // Only server-side rendering really needs this option (see
+ // `ReactServerRendering`), but server-side uses
+ // `ReactServerRenderingTransaction` instead. This option is here so that it's
+ // accessible and defaults to false when `ReactDOMComponent` and
+ // `ReactTextComponent` checks it in `mountComponent`.`
+ this.renderToStaticMarkup = false;
+ this.reactMountReady = CallbackQueue.getPooled(null);
+ this.putListenerQueue = ReactPutListenerQueue.getPooled();
+}
+
+var Mixin = {
+ /**
+ * @see Transaction
+ * @abstract
+ * @final
+ * @return {array} List of operation wrap proceedures.
+ * TODO: convert to array
+ */
+ getTransactionWrappers: function() {
+ return TRANSACTION_WRAPPERS;
+ },
+
+ /**
+ * @return {object} The queue to collect `onDOMReady` callbacks with.
+ */
+ getReactMountReady: function() {
+ return this.reactMountReady;
+ },
+
+ getPutListenerQueue: function() {
+ return this.putListenerQueue;
+ },
+
+ /**
+ * `PooledClass` looks for this, and will invoke this before allowing this
+ * instance to be resused.
+ */
+ destructor: function() {
+ CallbackQueue.release(this.reactMountReady);
+ this.reactMountReady = null;
+
+ ReactPutListenerQueue.release(this.putListenerQueue);
+ this.putListenerQueue = null;
+ }
+};
+
+
+mixInto(ReactWorkerReconcileTransaction, Transaction.Mixin);
+mixInto(ReactWorkerReconcileTransaction, Mixin);
+
+PooledClass.addPoolingTo(ReactWorkerReconcileTransaction);
+
+module.exports = ReactWorkerReconcileTransaction;
diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js
index 608fe62d8461a..8aa337cde8200 100644
--- a/src/core/ReactMultiChild.js
+++ b/src/core/ReactMultiChild.js
@@ -28,7 +28,7 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
/**
* Updating children of a component may trigger recursive updates. The depth is
- * used to batch recursive updates to render markup more efficiently.
+ * used to batch recursive updates to render image more efficiently.
*
* @type {number}
* @private
@@ -46,28 +46,28 @@ var updateDepth = 0;
var updateQueue = [];
/**
- * Queue of markup to be rendered.
+ * Queue of image to be rendered.
*
* @type {array}
* @private
*/
-var markupQueue = [];
+var imageQueue = [];
/**
- * Enqueues markup to be rendered and inserted at a supplied index.
+ * Enqueues image to be rendered and inserted at a supplied index.
*
* @param {string} parentID ID of the parent component.
- * @param {string} markup Markup that renders into an element.
+ * @param {string} image Image that renders into an element.
* @param {number} toIndex Destination index.
* @private
*/
-function enqueueMarkup(parentID, markup, toIndex) {
+function enqueueImage(parentID, image, toIndex) {
// NOTE: Null values reduce hidden classes.
updateQueue.push({
parentID: parentID,
parentNode: null,
- type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
- markupIndex: markupQueue.push(markup) - 1,
+ type: ReactMultiChildUpdateTypes.INSERT_IMAGE,
+ imageIndex: imageQueue.push(image) - 1,
textContent: null,
fromIndex: null,
toIndex: toIndex
@@ -88,7 +88,7 @@ function enqueueMove(parentID, fromIndex, toIndex) {
parentID: parentID,
parentNode: null,
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
- markupIndex: null,
+ imageIndex: null,
textContent: null,
fromIndex: fromIndex,
toIndex: toIndex
@@ -108,7 +108,7 @@ function enqueueRemove(parentID, fromIndex) {
parentID: parentID,
parentNode: null,
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
- markupIndex: null,
+ imageIndex: null,
textContent: null,
fromIndex: fromIndex,
toIndex: null
@@ -128,7 +128,7 @@ function enqueueTextContent(parentID, textContent) {
parentID: parentID,
parentNode: null,
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
- markupIndex: null,
+ imageIndex: null,
textContent: textContent,
fromIndex: null,
toIndex: null
@@ -144,7 +144,7 @@ function processQueue() {
if (updateQueue.length) {
ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates(
updateQueue,
- markupQueue
+ imageQueue
);
clearQueue();
}
@@ -157,7 +157,7 @@ function processQueue() {
*/
function clearQueue() {
updateQueue.length = 0;
- markupQueue.length = 0;
+ imageQueue.length = 0;
}
/**
@@ -179,7 +179,7 @@ var ReactMultiChild = {
/**
* Generates a "mount image" for each of the supplied children. In the case
- * of `ReactDOMComponent`, a mount image is a string of markup.
+ * of `ReactDOMComponent`, a mount image is a string of image.
*
* @param {?object} nestedChildren Nested child maps.
* @return {array} An array of mounted representations.
@@ -355,11 +355,11 @@ var ReactMultiChild = {
* Creates a child component.
*
* @param {ReactComponent} child Component to create.
- * @param {string} mountImage Markup to insert.
+ * @param {string} mountImage Image to insert.
* @protected
*/
createChild: function(child, mountImage) {
- enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex);
+ enqueueImage(this._rootNodeID, mountImage, child._mountIndex);
},
/**
diff --git a/src/core/ReactMultiChildUpdateTypes.js b/src/core/ReactMultiChildUpdateTypes.js
index 18cfd065e8490..7667346300cc4 100644
--- a/src/core/ReactMultiChildUpdateTypes.js
+++ b/src/core/ReactMultiChildUpdateTypes.js
@@ -29,7 +29,7 @@ var keyMirror = require('keyMirror');
* @internal
*/
var ReactMultiChildUpdateTypes = keyMirror({
- INSERT_MARKUP: null,
+ INSERT_IMAGE: null,
MOVE_EXISTING: null,
REMOVE_NODE: null,
TEXT_CONTENT: null
diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js
index 71067e15eb4dd..929ba0a0c8c9e 100644
--- a/src/core/__tests__/ReactComponent-test.js
+++ b/src/core/__tests__/ReactComponent-test.js
@@ -37,14 +37,14 @@ describe('ReactComponent', function() {
expect(function() {
React.renderComponent(
, [container]);
}).toThrow(
- 'Invariant Violation: _registerComponent(...): Target container ' +
+ 'Invariant Violation: getInstanceFromContainer(...): Target container ' +
'is not a DOM element.'
);
expect(function() {
React.renderComponent(
, null);
}).toThrow(
- 'Invariant Violation: _registerComponent(...): Target container ' +
+ 'Invariant Violation: getInstanceFromContainer(...): Target container ' +
'is not a DOM element.'
);
});
diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js
index e3a9d2af521a8..4649133db2ed3 100644
--- a/src/core/__tests__/ReactCompositeComponent-test.js
+++ b/src/core/__tests__/ReactCompositeComponent-test.js
@@ -30,6 +30,8 @@ var ReactPropTypes;
var ReactServerRendering;
var ReactTestUtils;
var TogglingComponent;
+var ReactDOMNodeMapping;
+var ReactDoNotBindDeprecated;
var cx;
var reactComponentExpect;
@@ -50,8 +52,8 @@ describe('ReactCompositeComponent', function() {
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
ReactPropTypes = require('ReactPropTypes');
ReactTestUtils = require('ReactTestUtils');
- ReactMount = require('ReactMount');
ReactServerRendering = require('ReactServerRendering');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
MorphingComponent = React.createClass({
getInitialState: function() {
@@ -316,7 +318,7 @@ describe('ReactCompositeComponent', function() {
// rerender
instance.setProps({renderAnchor: true, anchorClassOn: false});
var anchorID = instance.getAnchorID();
- var actualDOMAnchorNode = ReactMount.getNode(anchorID);
+ var actualDOMAnchorNode = ReactDOMNodeMapping.getNode(anchorID);
expect(actualDOMAnchorNode.className).toBe('');
});
@@ -812,7 +814,7 @@ describe('ReactCompositeComponent', function() {
var container = document.createElement('div');
var innerUnmounted = false;
- spyOn(ReactMount, 'purgeID').andCallThrough();
+ spyOn(ReactDOMNodeMapping, 'purgeID').andCallThrough();
var Component = React.createClass({
render: function() {
@@ -823,11 +825,11 @@ describe('ReactCompositeComponent', function() {
});
var Inner = React.createClass({
componentWillUnmount: function() {
- // It's important that ReactMount.purgeID be called after any component
+ // It's important that ReactDOMNodeMapping.purgeID be called after any component
// lifecycle methods, because a componentWillMount implementation is
// likely call this.getDOMNode(), which will repopulate the node cache
// after it's been cleared, causing a memory leak.
- expect(ReactMount.purgeID.callCount).toBe(0);
+ expect(ReactDOMNodeMapping.purgeID.callCount).toBe(0);
innerUnmounted = true;
},
render: function() {
@@ -841,7 +843,7 @@ describe('ReactCompositeComponent', function() {
// , , and both
elements each call
// unmountIDFromEnvironment which calls purgeID, for a total of 4.
- expect(ReactMount.purgeID.callCount).toBe(4);
+ expect(ReactDOMNodeMapping.purgeID.callCount).toBe(4);
});
it('should detect valid CompositeComponent classes', function() {
diff --git a/src/core/__tests__/ReactIdentity-test.js b/src/core/__tests__/ReactIdentity-test.js
index b07b2adcac3dc..4f6743b4e3732 100644
--- a/src/core/__tests__/ReactIdentity-test.js
+++ b/src/core/__tests__/ReactIdentity-test.js
@@ -22,7 +22,7 @@
var React;
var ReactTestUtils;
var reactComponentExpect;
-var ReactMount;
+var ReactDOMNodeMapping;
describe('ReactIdentity', function() {
@@ -31,12 +31,12 @@ describe('ReactIdentity', function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
reactComponentExpect = require('reactComponentExpect');
- ReactMount = require('ReactMount');
+ ReactDOMNodeMapping = require('ReactDOMNodeMapping');
});
var idExp = /^\.[^.]+(.*)$/;
function checkId(child, expectedId) {
- var actual = idExp.exec(ReactMount.getID(child));
+ var actual = idExp.exec(ReactDOMNodeMapping.getID(child));
var expected = idExp.exec(expectedId);
expect(actual).toBeTruthy();
expect(expected).toBeTruthy();
@@ -294,11 +294,11 @@ describe('ReactIdentity', function() {
wrapped = React.renderComponent(wrapped, document.createElement('div'));
- var beforeID = ReactMount.getID(wrapped.getDOMNode().firstChild);
+ var beforeID = ReactDOMNodeMapping.getID(wrapped.getDOMNode().firstChild);
wrapped.swap();
- var afterID = ReactMount.getID(wrapped.getDOMNode().firstChild);
+ var afterID = ReactDOMNodeMapping.getID(wrapped.getDOMNode().firstChild);
expect(beforeID).not.toEqual(afterID);
diff --git a/src/core/__tests__/ReactInstanceHandles-test.js b/src/core/__tests__/ReactInstanceHandles-test.js
index 4a5fe2243ee9e..b5b87b74d7b4c 100644
--- a/src/core/__tests__/ReactInstanceHandles-test.js
+++ b/src/core/__tests__/ReactInstanceHandles-test.js
@@ -21,7 +21,7 @@
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
/**
* Ensure that all callbacks are invoked, passing this unique argument.
@@ -78,7 +78,7 @@ describe('ReactInstanceHandles', function() {
describe('isRenderedByReact', function() {
it('should not crash on text nodes', function() {
expect(function() {
- ReactMount.isRenderedByReact(document.createTextNode('yolo'));
+ ReactDOMNodeMapping.isRenderedByReact(document.createTextNode('yolo'));
}).not.toThrow();
});
});
@@ -91,14 +91,14 @@ describe('ReactInstanceHandles', function() {
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
- ReactMount.setID(parentNode, '.0');
- ReactMount.setID(childNodeA, '.0.0');
- ReactMount.setID(childNodeB, '.0.0:1');
+ ReactDOMNodeMapping.setID(parentNode, '.0');
+ ReactDOMNodeMapping.setID(childNodeA, '.0.0');
+ ReactDOMNodeMapping.setID(childNodeB, '.0.0:1');
expect(
- ReactMount.findComponentRoot(
+ ReactDOMNodeMapping.findComponentRoot(
parentNode,
- ReactMount.getID(childNodeB)
+ ReactDOMNodeMapping.getID(childNodeB)
)
).toBe(childNodeB);
});
@@ -110,14 +110,14 @@ describe('ReactInstanceHandles', function() {
parentNode.appendChild(childNodeA);
parentNode.appendChild(childNodeB);
- ReactMount.setID(parentNode, '.0');
+ ReactDOMNodeMapping.setID(parentNode, '.0');
// No ID on `childNodeA`.
- ReactMount.setID(childNodeB, '.0.0:1');
+ ReactDOMNodeMapping.setID(childNodeB, '.0.0:1');
expect(
- ReactMount.findComponentRoot(
+ ReactDOMNodeMapping.findComponentRoot(
parentNode,
- ReactMount.getID(childNodeB)
+ ReactDOMNodeMapping.getID(childNodeB)
)
).toBe(childNodeB);
});
@@ -129,19 +129,19 @@ describe('ReactInstanceHandles', function() {
parentNode.appendChild(childNodeA);
childNodeA.appendChild(childNodeB);
- ReactMount.setID(parentNode, '.0');
+ ReactDOMNodeMapping.setID(parentNode, '.0');
// No ID on `childNodeA`, it was "rendered by the browser".
- ReactMount.setID(childNodeB, '.0.1:0');
+ ReactDOMNodeMapping.setID(childNodeB, '.0.1:0');
- expect(ReactMount.findComponentRoot(
+ expect(ReactDOMNodeMapping.findComponentRoot(
parentNode,
- ReactMount.getID(childNodeB)
+ ReactDOMNodeMapping.getID(childNodeB)
)).toBe(childNodeB);
expect(function() {
- ReactMount.findComponentRoot(
+ ReactDOMNodeMapping.findComponentRoot(
parentNode,
- ReactMount.getID(childNodeB) + ":junk"
+ ReactDOMNodeMapping.getID(childNodeB) + ":junk"
);
}).toThrow(
'Invariant Violation: findComponentRoot(..., .0.1:0:junk): ' +
diff --git a/src/core/__tests__/ReactMultiChildReconcile-test.js b/src/core/__tests__/ReactMultiChildReconcile-test.js
index a560ac13d2741..06066608faac9 100644
--- a/src/core/__tests__/ReactMultiChildReconcile-test.js
+++ b/src/core/__tests__/ReactMultiChildReconcile-test.js
@@ -23,7 +23,7 @@ require('mock-modules');
var React = require('React');
var ReactTestUtils = require('ReactTestUtils');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var mapObject = require('mapObject');
@@ -193,7 +193,7 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
var i;
var orderedDomIds = [];
for (i=0; i < statusDisplayNodes.length; i++) {
- orderedDomIds.push(ReactMount.getID(statusDisplayNodes[i]));
+ orderedDomIds.push(ReactDOMNodeMapping.getID(statusDisplayNodes[i]));
}
var orderedLogicalIds = [];
diff --git a/src/test/ReactDefaultPerf.js b/src/test/ReactDefaultPerf.js
index a302a65f06351..27df544c894ef 100644
--- a/src/test/ReactDefaultPerf.js
+++ b/src/test/ReactDefaultPerf.js
@@ -21,7 +21,7 @@
var DOMProperty = require('DOMProperty');
var ReactDefaultPerfAnalysis = require('ReactDefaultPerfAnalysis');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactPerf = require('ReactPerf');
var performanceNow = require('performanceNow');
@@ -175,7 +175,7 @@ var ReactDefaultPerf = {
totalTime = performanceNow() - start;
if (fnName === 'mountImageIntoNode') {
- var mountID = ReactMount.getID(args[1]);
+ var mountID = ReactDOMNodeMapping.getID(args[1]);
ReactDefaultPerf._recordWrite(mountID, fnName, totalTime, args[0]);
} else if (fnName === 'dangerouslyProcessChildrenUpdates') {
// special format
diff --git a/src/test/ReactDefaultPerfAnalysis.js b/src/test/ReactDefaultPerfAnalysis.js
index e497abc99086a..0d1f93adaf49e 100644
--- a/src/test/ReactDefaultPerfAnalysis.js
+++ b/src/test/ReactDefaultPerfAnalysis.js
@@ -29,7 +29,7 @@ var DOM_OPERATION_TYPES = {
'updatePropertyByID': 'update attribute',
'deletePropertyByID': 'delete attribute',
'updateStylesByID': 'update styles',
- 'updateInnerHTMLByID': 'set innerHTML',
+ 'updateImageByID': 'set innerHTML',
'dangerouslyReplaceNodeWithMarkupByID': 'replace'
};
diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js
index cac11e9bbae7f..c372eaf25e3ab 100644
--- a/src/test/ReactTestUtils.js
+++ b/src/test/ReactTestUtils.js
@@ -25,7 +25,7 @@ var React = require('React');
var ReactDescriptor = require('ReactDescriptor');
var ReactDOM = require('ReactDOM');
var ReactEventEmitter = require('ReactEventEmitter');
-var ReactMount = require('ReactMount');
+var ReactDOMNodeMapping = require('ReactDOMNodeMapping');
var ReactTextComponent = require('ReactTextComponent');
var ReactUpdates = require('ReactUpdates');
var SyntheticEvent = require('SyntheticEvent');
@@ -259,12 +259,11 @@ var ReactTestUtils = {
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
*/
simulateNativeEventOnNode: function(topLevelType, node, fakeNativeEvent) {
- var virtualHandler =
- ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
- topLevelType
- );
fakeNativeEvent.target = node;
- virtualHandler(fakeNativeEvent);
+ ReactEventEmitter.ReactEventListener.dispatchEvent(
+ topLevelType,
+ fakeNativeEvent
+ );
},
/**
@@ -320,7 +319,7 @@ function makeSimulator(eventType) {
// properly destroying any properties assigned from `eventData` upon release
var event = new SyntheticEvent(
ReactEventEmitter.eventNameDispatchConfigs[eventType],
- ReactMount.getID(node),
+ ReactDOMNodeMapping.getID(node),
fakeNativeEvent
);
mergeInto(event, eventData);
diff --git a/src/utils/__tests__/cloneWithProps-test.js b/src/utils/__tests__/cloneWithProps-test.js
index ab80af71939e7..c2c5cd14aadf0 100644
--- a/src/utils/__tests__/cloneWithProps-test.js
+++ b/src/utils/__tests__/cloneWithProps-test.js
@@ -28,6 +28,7 @@ var mocks = require('mocks');
var React;
var ReactTestUtils;
+var emptyObject;
var onlyChild;
var cloneWithProps;
var emptyObject;
@@ -37,6 +38,7 @@ describe('cloneWithProps', function() {
beforeEach(function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
+ emptyObject = require('emptyObject');
onlyChild = require('onlyChild');
cloneWithProps = require('cloneWithProps');
emptyObject = require('emptyObject');
diff --git a/src/vendor/core/ExecutionEnvironment.js b/src/vendor/core/ExecutionEnvironment.js
index 059eea4a97782..25bbde55993ac 100644
--- a/src/vendor/core/ExecutionEnvironment.js
+++ b/src/vendor/core/ExecutionEnvironment.js
@@ -20,8 +20,22 @@
"use strict";
+var invariant = require('invariant');
+
var canUseDOM = typeof window !== 'undefined';
+var globalObj;
+
+if (typeof window !== 'undefined') {
+ globalObj = window;
+} else if (typeof self !== 'undefined') {
+ globalObj = self;
+} else if (typeof global !== 'undefined') {
+ globalObj = global;
+}
+
+invariant(globalObj, 'ExecutionEnvironment: could not find global object');
+
/**
* Simple, lightweight module assisting with the detection and context of
* Worker. Helps avoid circular dependencies and allows code to reason about
@@ -37,7 +51,9 @@ var ExecutionEnvironment = {
canUseEventListeners:
canUseDOM && (window.addEventListener || window.attachEvent),
- isInWorker: !canUseDOM // For now, this is true - might change in the future.
+ isInWorker: !canUseDOM, // For now, this is true - might change in the future.
+
+ global: globalObj
};