UNPKG

6.27 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 */
10
11'use strict';
12
13var _prodInvariant = require('./reactProdInvariant');
14
15var DOMProperty = require('./DOMProperty');
16var ReactDOMComponentFlags = require('./ReactDOMComponentFlags');
17
18var invariant = require('fbjs/lib/invariant');
19
20var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
21var Flags = ReactDOMComponentFlags;
22
23var internalInstanceKey = '__reactInternalInstance$' + Math.random().toString(36).slice(2);
24
25/**
26 * Check if a given node should be cached.
27 */
28function shouldPrecacheNode(node, nodeID) {
29 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 + ' ';
30}
31
32/**
33 * Drill down (through composites and empty components) until we get a host or
34 * host text component.
35 *
36 * This is pretty polymorphic but unavoidable with the current structure we have
37 * for `_renderedChildren`.
38 */
39function getRenderedHostOrTextFromComponent(component) {
40 var rendered;
41 while (rendered = component._renderedComponent) {
42 component = rendered;
43 }
44 return component;
45}
46
47/**
48 * Populate `_hostNode` on the rendered host/text component with the given
49 * DOM node. The passed `inst` can be a composite.
50 */
51function precacheNode(inst, node) {
52 var hostInst = getRenderedHostOrTextFromComponent(inst);
53 hostInst._hostNode = node;
54 node[internalInstanceKey] = hostInst;
55}
56
57function uncacheNode(inst) {
58 var node = inst._hostNode;
59 if (node) {
60 delete node[internalInstanceKey];
61 inst._hostNode = null;
62 }
63}
64
65/**
66 * Populate `_hostNode` on each child of `inst`, assuming that the children
67 * match up with the DOM (element) children of `node`.
68 *
69 * We cache entire levels at once to avoid an n^2 problem where we access the
70 * children of a node sequentially and have to walk from the start to our target
71 * node every time.
72 *
73 * Since we update `_renderedChildren` and the actual DOM at (slightly)
74 * different times, we could race here and see a newer `_renderedChildren` than
75 * the DOM nodes we see. To avoid this, ReactMultiChild calls
76 * `prepareToManageChildren` before we change `_renderedChildren`, at which
77 * time the container's child nodes are always cached (until it unmounts).
78 */
79function precacheChildNodes(inst, node) {
80 if (inst._flags & Flags.hasCachedChildNodes) {
81 return;
82 }
83 var children = inst._renderedChildren;
84 var childNode = node.firstChild;
85 outer: for (var name in children) {
86 if (!children.hasOwnProperty(name)) {
87 continue;
88 }
89 var childInst = children[name];
90 var childID = getRenderedHostOrTextFromComponent(childInst)._domID;
91 if (childID === 0) {
92 // We're currently unmounting this child in ReactMultiChild; skip it.
93 continue;
94 }
95 // We assume the child nodes are in the same order as the child instances.
96 for (; childNode !== null; childNode = childNode.nextSibling) {
97 if (shouldPrecacheNode(childNode, childID)) {
98 precacheNode(childInst, childNode);
99 continue outer;
100 }
101 }
102 // We reached the end of the DOM children without finding an ID match.
103 !false ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Unable to find element with ID %s.', childID) : _prodInvariant('32', childID) : void 0;
104 }
105 inst._flags |= Flags.hasCachedChildNodes;
106}
107
108/**
109 * Given a DOM node, return the closest ReactDOMComponent or
110 * ReactDOMTextComponent instance ancestor.
111 */
112function getClosestInstanceFromNode(node) {
113 if (node[internalInstanceKey]) {
114 return node[internalInstanceKey];
115 }
116
117 // Walk up the tree until we find an ancestor whose instance we have cached.
118 var parents = [];
119 while (!node[internalInstanceKey]) {
120 parents.push(node);
121 if (node.parentNode) {
122 node = node.parentNode;
123 } else {
124 // Top of the tree. This node must not be part of a React tree (or is
125 // unmounted, potentially).
126 return null;
127 }
128 }
129
130 var closest;
131 var inst;
132 for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
133 closest = inst;
134 if (parents.length) {
135 precacheChildNodes(inst, node);
136 }
137 }
138
139 return closest;
140}
141
142/**
143 * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
144 * instance, or null if the node was not rendered by this React.
145 */
146function getInstanceFromNode(node) {
147 var inst = getClosestInstanceFromNode(node);
148 if (inst != null && inst._hostNode === node) {
149 return inst;
150 } else {
151 return null;
152 }
153}
154
155/**
156 * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
157 * DOM node.
158 */
159function getNodeFromInstance(inst) {
160 // Without this first invariant, passing a non-DOM-component triggers the next
161 // invariant for a missing parent, which is super confusing.
162 !(inst._hostNode !== undefined) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getNodeFromInstance: Invalid argument.') : _prodInvariant('33') : void 0;
163
164 if (inst._hostNode) {
165 return inst._hostNode;
166 }
167
168 // Walk up the tree until we find an ancestor whose DOM node we have cached.
169 var parents = [];
170 while (!inst._hostNode) {
171 parents.push(inst);
172 !inst._hostParent ? process.env.NODE_ENV !== 'production' ? invariant(false, 'React DOM tree root should always have a node reference.') : _prodInvariant('34') : void 0;
173 inst = inst._hostParent;
174 }
175
176 // Now parents contains each ancestor that does *not* have a cached native
177 // node, and `inst` is the deepest ancestor that does.
178 for (; parents.length; inst = parents.pop()) {
179 precacheChildNodes(inst, inst._hostNode);
180 }
181
182 return inst._hostNode;
183}
184
185var ReactDOMComponentTree = {
186 getClosestInstanceFromNode: getClosestInstanceFromNode,
187 getInstanceFromNode: getInstanceFromNode,
188 getNodeFromInstance: getNodeFromInstance,
189 precacheChildNodes: precacheChildNodes,
190 precacheNode: precacheNode,
191 uncacheNode: uncacheNode
192};
193
194module.exports = ReactDOMComponentTree;
\No newline at end of file