diff --git a/src/renderers/dom/client/ReactDOMComponentTree.js b/src/renderers/dom/client/ReactDOMComponentTree.js new file mode 100644 index 0000000000000..99dcb2654ae78 --- /dev/null +++ b/src/renderers/dom/client/ReactDOMComponentTree.js @@ -0,0 +1,188 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMComponentTree + */ + +'use strict'; + +var DOMProperty = require('DOMProperty'); +var ReactDOMComponentFlags = require('ReactDOMComponentFlags'); + +var invariant = require('invariant'); + +var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; +var Flags = ReactDOMComponentFlags; + +var internalInstanceKey = + '__reactInternalInstance$' + Math.random().toString(36).slice(2); + +/** + * Drill down (through composites and empty components) until we get a native or + * native text component. + * + * This is pretty polymorphic but unavoidable with the current structure we have + * for `_renderedChildren`. + */ +function getRenderedNativeOrTextFromComponent(component) { + var rendered; + while ((rendered = component._renderedComponent)) { + component = rendered; + } + return component; +} + +/** + * Populate `_nativeNode` on the rendered native/text component with the given + * DOM node. The passed `inst` can be a composite. + */ +function precacheNode(inst, node) { + var nativeInst = getRenderedNativeOrTextFromComponent(inst); + nativeInst._nativeNode = node; + node[internalInstanceKey] = nativeInst; +} + +function uncacheNode(inst) { + var node = inst._nativeNode; + if (node) { + delete node[internalInstanceKey]; + inst._nativeNode = null; + } +} + +/** + * Populate `_nativeNode` on each child of `inst`, assuming that the children + * match up with the DOM (element) children of `node`. + * + * We cache entire levels at once to avoid an n^2 problem where we access the + * children of a node sequentially and have to walk from the start to our target + * node every time. + * + * Since we update `_renderedChildren` and the actual DOM at (slightly) + * different times, we could race here and not get the + */ +function precacheChildNodes(inst, node) { + if (inst._flags & Flags.hasCachedChildNodes) { + return; + } + var children = inst._renderedChildren; + var childNode = node.firstChild; + outer: for (var name in children) { + if (!children.hasOwnProperty(name)) { + continue; + } + var childInst = children[name]; + var childID = childInst._rootNodeID; + if (childID == null) { + // We're currently unmounting this child in ReactMultiChild; skip it. + continue; + } + // We assume the child nodes are in the same order as the child instances. + for (; childNode !== null; childNode = childNode.nextSibling) { + if (childNode.nodeType === 1 && + childNode.getAttribute(ATTR_NAME) === childID) { + precacheNode(childInst, childNode); + continue outer; + } + } + // We reached the end of the DOM children without finding an ID match. + invariant(false, 'Unable to find element with ID %s.', childID); + } + inst._flags |= Flags.hasCachedChildNodes; +} + +/** + * Given a DOM node, return the closest ReactDOMComponent or + * ReactDOMTextComponent instance ancestor. + */ +function getClosestInstanceFromNode(node) { + if (node[internalInstanceKey]) { + return node[internalInstanceKey]; + } + + // Walk up the tree until we find an ancestor whose instance we have cached. + var parents = []; + while (!node[internalInstanceKey]) { + parents.push(node); + if (node.parentNode) { + node = node.parentNode; + } else { + // Top of the tree. This node must not be part of a React tree (or is + // unmounted, potentially). + return null; + } + } + + var closest; + var inst; + for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) { + closest = inst; + if (parents.length) { + precacheChildNodes(inst, node); + } + } + + return closest; +} + +/** + * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent + * instance, or null if the node was not rendered by this React. + */ +function getInstanceFromNode(node) { + var inst = getClosestInstanceFromNode(node); + if (inst != null && inst._nativeNode === node) { + return inst; + } else { + return null; + } +} + +/** + * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding + * DOM node. + */ +function getNodeFromInstance(inst) { + // Without this first invariant, passing a non-DOM-component causes the next + // invariant for a missing parent, which is super confusing. + invariant('_nativeNode' in inst, 'getNodeFromInstance: Invalid argument.'); + + if (inst._nativeNode) { + return inst._nativeNode; + } + + // Walk up the tree until we find an ancestor whose DOM node we have cached. + var parents = []; + while (!inst._nativeNode) { + parents.push(inst); + invariant( + inst._nativeParent, + 'React DOM tree root should always have a node reference.' + ); + inst = inst._nativeParent; + } + + // Now parents contains each ancestor that does *not* have a cached native + // node, and `inst` is the deepest ancestor that does. + for (; parents.length; inst = parents.pop()) { + precacheChildNodes(inst, inst._nativeNode); + } + + return inst._nativeNode; +} + +var ReactDOMComponentTree = { + getClosestInstanceFromNode: getClosestInstanceFromNode, + getInstanceFromNode: getInstanceFromNode, + getNodeFromInstance: getNodeFromInstance, + precacheChildNodes: precacheChildNodes, + precacheNode: precacheNode, + uncacheNode: uncacheNode, +}; + +module.exports = ReactDOMComponentTree; diff --git a/src/renderers/dom/client/ReactDOMIDOperations.js b/src/renderers/dom/client/ReactDOMIDOperations.js index 7b419e2bf9663..98bd1df70f1ff 100644 --- a/src/renderers/dom/client/ReactDOMIDOperations.js +++ b/src/renderers/dom/client/ReactDOMIDOperations.js @@ -13,56 +13,14 @@ 'use strict'; var DOMChildrenOperations = require('DOMChildrenOperations'); -var DOMPropertyOperations = require('DOMPropertyOperations'); -var ReactMount = require('ReactMount'); +var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactPerf = require('ReactPerf'); -var invariant = require('invariant'); - -/** - * Errors for properties that should not be updated with `updatePropertyByID()`. - * - * @type {object} - * @private - */ -var INVALID_PROPERTY_ERRORS = { - dangerouslySetInnerHTML: - '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.', - style: '`style` must be set using `updateStylesByID()`.', -}; - /** * Operations used to process updates to DOM nodes. */ var ReactDOMIDOperations = { - /** - * Updates a DOM node with new property values. This should only be used to - * update DOM properties in `DOMProperty`. - * - * @param {string} id ID of the node to update. - * @param {string} name A valid property name, see `DOMProperty`. - * @param {*} value New value of the property. - * @internal - */ - updatePropertyByID: function(id, name, value) { - var node = ReactMount.getNode(id); - invariant( - !INVALID_PROPERTY_ERRORS.hasOwnProperty(name), - 'updatePropertyByID(...): %s', - INVALID_PROPERTY_ERRORS[name] - ); - - // If we're updating to null or undefined, we should remove the property - // from the DOM node instead of inadvertantly setting to a string. This - // brings us in line with the same behavior we have on initial render. - if (value != null) { - DOMPropertyOperations.setValueForProperty(node, name, value); - } else { - DOMPropertyOperations.deleteValueForProperty(node, name); - } - }, - /** * Updates a component's children by processing a series of updates. * @@ -72,7 +30,9 @@ var ReactDOMIDOperations = { */ dangerouslyProcessChildrenUpdates: function(updates, markup) { for (var i = 0; i < updates.length; i++) { - updates[i].parentNode = ReactMount.getNode(updates[i].parentID); + var update = updates[i]; + var node = ReactDOMComponentTree.getNodeFromInstance(update.parentInst); + update.parentNode = node; } DOMChildrenOperations.processUpdates(updates, markup); }, diff --git a/src/renderers/dom/client/ReactDOMTreeTraversal.js b/src/renderers/dom/client/ReactDOMTreeTraversal.js new file mode 100644 index 0000000000000..726a051bebf18 --- /dev/null +++ b/src/renderers/dom/client/ReactDOMTreeTraversal.js @@ -0,0 +1,134 @@ +/** + * Copyright 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactDOMTreeTraversal + */ + +'use strict'; + +var invariant = require('invariant'); + +/** + * Return the lowest common ancestor of A and B, or null if they are in + * different trees. + */ +function getLowestCommonAncestor(instA, instB) { + invariant('_nativeNode' in instA, 'getNodeFromInstance: Invalid argument.'); + invariant('_nativeNode' in instB, 'getNodeFromInstance: Invalid argument.'); + + var depthA = 0; + for (var tempA = instA; tempA; tempA = tempA._nativeParent) { + depthA++; + } + var depthB = 0; + for (var tempB = instB; tempB; tempB = tempB._nativeParent) { + depthB++; + } + + // If A is deeper, crawl up. + while (depthA - depthB > 0) { + instA = instA._nativeParent; + depthA--; + } + + // If B is deeper, crawl up. + while (depthB - depthA > 0) { + instB = instB._nativeParent; + depthB--; + } + + // Walk in lockstep until we find a match. + var depth = depthA; + while (depth--) { + if (instA === instB) { + return instA; + } + instA = instA._nativeParent; + instB = instB._nativeParent; + } + return null; +} + +/** + * Return if A is an ancestor of B. + */ +function isAncestor(instA, instB) { + invariant('_nativeNode' in instA, 'isAncestor: Invalid argument.'); + invariant('_nativeNode' in instB, 'isAncestor: Invalid argument.'); + + while (instB) { + if (instB === instA) { + return true; + } + instB = instB._nativeParent; + } + return false; +} + +/** + * Return the parent instance of the passed-in instance. + */ +function getParentInstance(inst) { + invariant('_nativeNode' in inst, 'getParentInstance: Invalid argument.'); + + return inst._nativeParent; +} + +/** + * Simulates the traversal of a two-phase, capture/bubble event dispatch. + */ +function traverseTwoPhase(inst, fn, arg) { + var path = []; + while (inst) { + path.push(inst); + inst = inst._nativeParent; + } + var i; + for (i = path.length; i-- > 0;) { + fn(path[i], false, arg); + } + for (i = 0; i < path.length; i++) { + fn(path[i], true, arg); + } +} + +/** + * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that + * should would receive a `mouseEnter` or `mouseLeave` event. + * + * Does not invoke the callback on the nearest common ancestor because nothing + * "entered" or "left" that element. + */ +function traverseEnterLeave(from, to, fn, argFrom, argTo) { + var common = from && to ? getLowestCommonAncestor(from, to) : null; + var pathFrom = []; + while (from && from !== common) { + pathFrom.push(from); + from = from._nativeParent; + } + var pathTo = []; + while (to && to !== common) { + pathTo.push(to); + to = to._nativeParent; + } + var i; + for (i = 0; i < pathFrom.length; i++) { + fn(pathFrom[i], true, argFrom); + } + for (i = pathTo.length; i-- > 0;) { + fn(pathTo[i], false, argTo); + } +} + +module.exports = { + isAncestor: isAncestor, + getLowestCommonAncestor: getLowestCommonAncestor, + getParentInstance: getParentInstance, + traverseTwoPhase: traverseTwoPhase, + traverseEnterLeave: traverseEnterLeave, +}; diff --git a/src/renderers/dom/client/ReactEventListener.js b/src/renderers/dom/client/ReactEventListener.js index 422b0ee650ee2..307e5debad145 100644 --- a/src/renderers/dom/client/ReactEventListener.js +++ b/src/renderers/dom/client/ReactEventListener.js @@ -15,32 +15,28 @@ var EventListener = require('EventListener'); var ExecutionEnvironment = require('ExecutionEnvironment'); var PooledClass = require('PooledClass'); -var ReactInstanceHandles = require('ReactInstanceHandles'); -var ReactMount = require('ReactMount'); +var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactUpdates = require('ReactUpdates'); var assign = require('Object.assign'); var getEventTarget = require('getEventTarget'); var getUnboundedScrollPosition = require('getUnboundedScrollPosition'); -var DOCUMENT_FRAGMENT_NODE_TYPE = 11; - /** - * Finds the parent React component of `node`. - * - * @param {*} node - * @return {?DOMEventTarget} Parent container, or `null` if the specified node - * is not nested. + * Find the deepest React component completely containing the root of the + * passed-in instance (for use when entire React trees are nested within each + * other). If React trees are not nested, returns null. */ -function findParent(node) { +function findParent(inst) { // 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; + while (inst._nativeParent) { + inst = inst._nativeParent; + } + var rootNode = ReactDOMComponentTree.getNodeFromInstance(inst); + var container = rootNode.parentNode; + return ReactDOMComponentTree.getClosestInstanceFromNode(container); } // Used to store ancestor hierarchy in top level callback @@ -62,91 +58,26 @@ PooledClass.addPoolingTo( ); function handleTopLevelImpl(bookKeeping) { - // TODO: Re-enable event.path handling - // - // if (bookKeeping.nativeEvent.path && bookKeeping.nativeEvent.path.length > 1) { - // // New browsers have a path attribute on native events - // handleTopLevelWithPath(bookKeeping); - // } else { - // // Legacy browsers don't have a path attribute on native events - // handleTopLevelWithoutPath(bookKeeping); - // } - - void handleTopLevelWithPath; // temporarily unused - handleTopLevelWithoutPath(bookKeeping); -} - -// Legacy browsers don't have a path attribute on native events -function handleTopLevelWithoutPath(bookKeeping) { - var topLevelTarget = ReactMount.getFirstReactDOM( - getEventTarget(bookKeeping.nativeEvent) - ) || window; + var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent); + var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode( + nativeEventTarget + ); // 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) { + var ancestor = targetInst; + do { bookKeeping.ancestors.push(ancestor); - ancestor = findParent(ancestor); - } + ancestor = ancestor && findParent(ancestor); + } while (ancestor); for (var i = 0; i < bookKeeping.ancestors.length; i++) { - topLevelTarget = bookKeeping.ancestors[i]; - var topLevelTargetID = ReactMount.getID(topLevelTarget) || ''; - ReactEventListener._handleTopLevel( - bookKeeping.topLevelType, - topLevelTarget, - topLevelTargetID, - bookKeeping.nativeEvent, - getEventTarget(bookKeeping.nativeEvent) - ); - } -} - -// New browsers have a path attribute on native events -function handleTopLevelWithPath(bookKeeping) { - var path = bookKeeping.nativeEvent.path; - var currentNativeTarget = path[0]; - var eventsFired = 0; - for (var i = 0; i < path.length; i++) { - var currentPathElement = path[i]; - if (currentPathElement.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE) { - currentNativeTarget = path[i + 1]; - } - // TODO: slow - var reactParent = ReactMount.getFirstReactDOM(currentPathElement); - if (reactParent === currentPathElement) { - var currentPathElementID = ReactMount.getID(currentPathElement); - var newRootID = ReactInstanceHandles.getReactRootIDFromNodeID( - currentPathElementID - ); - bookKeeping.ancestors.push(currentPathElement); - - var topLevelTargetID = ReactMount.getID(currentPathElement) || ''; - eventsFired++; - ReactEventListener._handleTopLevel( - bookKeeping.topLevelType, - currentPathElement, - topLevelTargetID, - bookKeeping.nativeEvent, - currentNativeTarget - ); - - // Jump to the root of this React render tree - while (currentPathElementID !== newRootID) { - i++; - currentPathElement = path[i]; - currentPathElementID = ReactMount.getID(currentPathElement); - } - } - } - if (eventsFired === 0) { + targetInst = bookKeeping.ancestors[i]; ReactEventListener._handleTopLevel( bookKeeping.topLevelType, - window, - '', + targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent) ); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index a1704f47fce92..88ed3a01a854c 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -16,7 +16,9 @@ var DOMLazyTree = require('DOMLazyTree'); var DOMProperty = require('DOMProperty'); var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactCurrentOwner = require('ReactCurrentOwner'); +var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactElement = require('ReactElement'); var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); @@ -26,7 +28,6 @@ var ReactUpdateQueue = require('ReactUpdateQueue'); var ReactUpdates = require('ReactUpdates'); var emptyObject = require('emptyObject'); -var containsNode = require('containsNode'); var instantiateReactComponent = require('instantiateReactComponent'); var invariant = require('invariant'); var setInnerHTML = require('setInnerHTML'); @@ -34,7 +35,6 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var warning = require('warning'); var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; -var nodeCache = {}; var ELEMENT_NODE_TYPE = 1; var DOC_NODE_TYPE = 9; @@ -52,9 +52,6 @@ if (__DEV__) { var rootElementsByReactRootID = {}; } -// Used to store breadth-first search state in findComponentRoot. -var findComponentRootReusableArray = []; - /** * Finds the index of the first character * that's not common between the two given strings. @@ -97,33 +94,6 @@ function getReactRootID(container) { return rootElement && internalGetID(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) { - var cached = nodeCache[id]; - // TODO: Fix this whole concept of "validity" -- the cache just shouldn't - // have nodes that have been unmounted. - invariant( - !cached || cached === node || !isValid(cached, id), - 'ReactMount: Two valid but unequal nodes with the same `%s`: %s', - ATTR_NAME, id - ); - 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 @@ -131,112 +101,6 @@ function internalGetID(node) { 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 ID if present in the cache. - */ -function getNodeIfCached(id) { - var node = nodeCache[id]; - // TODO: Fix this whole concept of "validity" -- the cache just shouldn't have - // nodes that have been unmounted. - if (node && isValid(node, id)) { - return 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) { - var node = getNodeIfCached(id); - if (node) { - return node; - } else { - return nodeCache[id] = ReactMount.findReactNodeByID(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 = getNodeIfCached(ancestorID); - if (ancestor) { - 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; -} - /** * Mounts this component and inserts it into the DOM. * @@ -266,6 +130,7 @@ function mountComponentIntoNode( ReactMount._mountImageIntoNode( markup, container, + componentInstance, shouldReuseMarkup, transaction ); @@ -287,7 +152,8 @@ function batchedMountComponentIntoNode( context ) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( - /* forceHTML */ shouldReuseMarkup + /* useCreateElement */ + !shouldReuseMarkup && ReactDOMFeatureFlags.useCreateElement ); transaction.perform( mountComponentIntoNode, @@ -340,47 +206,6 @@ function hasNonRootReactChild(node) { ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false; } -/** - * Returns the first (deepest) ancestor of a node which is rendered by this copy - * of React. - */ -function findFirstReactDOMImpl(node) { - // This node might be from another React instance, so we make sure not to - // examine the node cache here - for (; node && node.parentNode !== node; node = node.parentNode) { - if (node.nodeType !== 1) { - // Not a DOMElement, therefore not a React component - continue; - } - var nodeID = internalGetID(node); - if (!nodeID) { - continue; - } - var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(nodeID); - - // If containersByReactRootID contains the container we find by crawling up - // the tree, we know that this instance of React rendered the node. - // nb. isValid's strategy (with containsNode) does not work because render - // trees may be nested and we don't want a false positive in that case. - var current = node; - var lastID; - do { - lastID = internalGetID(current); - current = current.parentNode; - if (current == null) { - // The passed-in node has been detached from the container it was - // originally rendered into. - return null; - } - } while (lastID !== reactRootID); - - if (current === containersByReactRootID[reactRootID]) { - return node; - } - } - return null; -} - /** * Temporary (?) hack so that we can store all top-level pending updates on * composites instead of having to worry about different types of components @@ -790,165 +615,10 @@ var ReactMount = { return true; }, - /** - * 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) { - warning( - // 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 { - warning( - false, - 'ReactMount: Root element has been removed from its original ' + - 'container. New container: %s', - 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); - }, - - /** - * Traverses up the ancestors of the supplied node to find a node that is a - * DOM representation of a React component rendered by this copy of React. - * - * @param {*} node - * @return {?DOMEventTarget} - * @internal - */ - getFirstReactDOM: function(node) { - return findFirstReactDOMImpl(node); - }, - - /** - * 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; - - if (__DEV__) { - // This will throw on the next line; give an early warning - warning( - deepestAncestor != null, - 'React can\'t find the root component node for data-reactid value ' + - '`%s`. If you\'re seeing this message, it probably means that ' + - 'you\'ve loaded two copies of React on the page. At this time, only ' + - 'a single copy of React can be loaded at a time.', - targetID - ); - } - - 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 `