194 lines
6.1 KiB
JavaScript
194 lines
6.1 KiB
JavaScript
|
/**
|
||
|
* Copyright 2013-present, 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var _prodInvariant = require('./reactProdInvariant');
|
||
|
|
||
|
var DOMProperty = require('./DOMProperty');
|
||
|
var ReactDOMComponentFlags = require('./ReactDOMComponentFlags');
|
||
|
|
||
|
var invariant = require('fbjs/lib/invariant');
|
||
|
|
||
|
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
|
||
|
var Flags = ReactDOMComponentFlags;
|
||
|
|
||
|
var internalInstanceKey = '__reactInternalInstance$' + Math.random().toString(36).slice(2);
|
||
|
|
||
|
/**
|
||
|
* Check if a given node should be cached.
|
||
|
*/
|
||
|
function shouldPrecacheNode(node, nodeID) {
|
||
|
return node.nodeType === 1 && node.getAttribute(ATTR_NAME) === String(nodeID) || node.nodeType === 8 && node.nodeValue === ' react-text: ' + nodeID + ' ' || node.nodeType === 8 && node.nodeValue === ' react-empty: ' + nodeID + ' ';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Drill down (through composites and empty components) until we get a host or
|
||
|
* host text component.
|
||
|
*
|
||
|
* This is pretty polymorphic but unavoidable with the current structure we have
|
||
|
* for `_renderedChildren`.
|
||
|
*/
|
||
|
function getRenderedHostOrTextFromComponent(component) {
|
||
|
var rendered;
|
||
|
while (rendered = component._renderedComponent) {
|
||
|
component = rendered;
|
||
|
}
|
||
|
return component;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Populate `_hostNode` on the rendered host/text component with the given
|
||
|
* DOM node. The passed `inst` can be a composite.
|
||
|
*/
|
||
|
function precacheNode(inst, node) {
|
||
|
var hostInst = getRenderedHostOrTextFromComponent(inst);
|
||
|
hostInst._hostNode = node;
|
||
|
node[internalInstanceKey] = hostInst;
|
||
|
}
|
||
|
|
||
|
function uncacheNode(inst) {
|
||
|
var node = inst._hostNode;
|
||
|
if (node) {
|
||
|
delete node[internalInstanceKey];
|
||
|
inst._hostNode = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Populate `_hostNode` 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 see a newer `_renderedChildren` than
|
||
|
* the DOM nodes we see. To avoid this, ReactMultiChild calls
|
||
|
* `prepareToManageChildren` before we change `_renderedChildren`, at which
|
||
|
* time the container's child nodes are always cached (until it unmounts).
|
||
|
*/
|
||
|
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 = getRenderedHostOrTextFromComponent(childInst)._domID;
|
||
|
if (childID === 0) {
|
||
|
// 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 (shouldPrecacheNode(childNode, childID)) {
|
||
|
precacheNode(childInst, childNode);
|
||
|
continue outer;
|
||
|
}
|
||
|
}
|
||
|
// We reached the end of the DOM children without finding an ID match.
|
||
|
!false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Unable to find element with ID %s.', childID) : _prodInvariant('32', childID) : void 0;
|
||
|
}
|
||
|
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._hostNode === 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 triggers the next
|
||
|
// invariant for a missing parent, which is super confusing.
|
||
|
!(inst._hostNode !== undefined) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getNodeFromInstance: Invalid argument.') : _prodInvariant('33') : void 0;
|
||
|
|
||
|
if (inst._hostNode) {
|
||
|
return inst._hostNode;
|
||
|
}
|
||
|
|
||
|
// Walk up the tree until we find an ancestor whose DOM node we have cached.
|
||
|
var parents = [];
|
||
|
while (!inst._hostNode) {
|
||
|
parents.push(inst);
|
||
|
!inst._hostParent ? process.env.NODE_ENV !== 'production' ? invariant(false, 'React DOM tree root should always have a node reference.') : _prodInvariant('34') : void 0;
|
||
|
inst = inst._hostParent;
|
||
|
}
|
||
|
|
||
|
// 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._hostNode);
|
||
|
}
|
||
|
|
||
|
return inst._hostNode;
|
||
|
}
|
||
|
|
||
|
var ReactDOMComponentTree = {
|
||
|
getClosestInstanceFromNode: getClosestInstanceFromNode,
|
||
|
getInstanceFromNode: getInstanceFromNode,
|
||
|
getNodeFromInstance: getNodeFromInstance,
|
||
|
precacheChildNodes: precacheChildNodes,
|
||
|
precacheNode: precacheNode,
|
||
|
uncacheNode: uncacheNode
|
||
|
};
|
||
|
|
||
|
module.exports = ReactDOMComponentTree;
|