19
20
"use strict";
21
22
+var invariant = require('invariant');
23
var ReactMount = require('ReactMount');
24
var ATTR_NAME = 'id';
25
var nodeCache = {};
35
* @return {string} ID of the supplied `domNode`.
36
*/
37
function getID(node) {
37
- if (node && node.getAttributeNode) {
38
- var attributeNode = node.getAttributeNode(ATTR_NAME);
39
- if (attributeNode) {
40
- var id = attributeNode.value;
41
- if (id) {
42
- // Assume that any node previously cached with this ID has since
43
- // become invalid and should be replaced. TODO Enforce this.
44
- nodeCache[id] = node;
38
+ var id = internalGetID(node);
39
+ if (id) {
40
+ if (nodeCache.hasOwnProperty(id)) {
41
+ var cached = nodeCache[id];
42
+ if (cached !== node) {
43
+ invariant(
44
+ !isValid(cached, id),
45
+ 'ReactID: Two valid but unequal nodes with the same `%s`: %s',
46
+ ATTR_NAME, id
47
+ );
48
46
- return id;
49
+ nodeCache[id] = node;
50
}
51
+ } else {
52
+ nodeCache[id] = node;
53
}
54
}
55
56
+ return id;
57
+}
58
+
59
+function internalGetID(node) {
60
+ if (node && node.getAttributeNode) {
61
+ var attributeNode = node.getAttributeNode(ATTR_NAME);
62
+ if (attributeNode) {
63
+ return attributeNode.value || '';
64
+ }
65
+ }
66
return '';
67
}
68
73
* @param {string} id The value of the ID attribute.
74
*/
75
function setID(node, id) {
61
- var oldID = getID(node);
76
+ var oldID = internalGetID(node);
77
if (oldID !== id) {
78
delete nodeCache[oldID];
79
}
89
* @internal
90
*/
91
function getNode(id) {
77
- if (!nodeCache[id]) {
78
- nodeCache[id] =
79
- document.getElementById(id) || // TODO Quit using getElementById.
80
- ReactMount.findReactRenderedDOMNodeSlow(id);
92
+ if (nodeCache.hasOwnProperty(id)) {
93
+ var node = nodeCache[id];
94
+ if (isValid(node, id)) {
95
+ return node;
96
+ }
97
+ }
98
+
99
+ return nodeCache[id] =
100
+ document.getElementById(id) || // TODO Quit using getElementById.
101
+ ReactMount.findReactRenderedDOMNodeSlow(id);
102
+}
103
+
104
+/**
105
+ * A node is "valid" if it is contained by a currently mounted container.
106
+ *
107
+ * This means that the node does not have to be contained by a document in
108
+ * order to be considered valid.
109
+ *
110
+ * @param {?DOMElement} node The candidate DOM node.
111
+ * @param {string} id The expected ID of the node.
112
+ * @return {boolean} Whether the node is contained by a mounted container.
113
+ */
114
+function isValid(node, id) {
115
+ if (node) {
116
+ invariant(
117
+ internalGetID(node) === id,
118
+ 'ReactID: Unexpected modification of `%s`',
119
+ ATTR_NAME
120
+ );
121
+
122
+ var container = ReactMount.findReactContainerForID(id);
123
+ if (container && contains(container, node)) {
124
+ return true;
125
+ }
126
+ }
127
+
128
+ return false;
129
+}
130
+
131
+function contains(ancestor, descendant) {
132
+ if (ancestor.contains) {
133
+ // Supported natively in virtually all browsers, but not in jsdom.
134
+ return ancestor.contains(descendant);
135
+ }
136
+
137
+ if (descendant === ancestor) {
138
+ return true;
139
+ }
140
+
141
+ if (descendant.nodeType === 3) {
142
+ // If descendant is a text node, start from descendant.parentNode
143
+ // instead, so that we can assume all ancestors worth considering are
144
+ // element nodes with nodeType === 1.
145
+ descendant = descendant.parentNode;
146
}
147
83
- var node = nodeCache[id];
84
- if (getID(node) === id) {
85
- return node;
148
+ while (descendant && descendant.nodeType === 1) {
149
+ if (descendant === ancestor) {
150
+ return true;
151
+ }
152
+ descendant = descendant.parentNode;
153
}
154
88
- return null;
155
+ return false;
156
}
157
158
/**