UNPKG

10.4 kBJavaScriptView Raw
1/**
2 * Copyright 2016-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
12'use strict';
13
14var _prodInvariant = require('./reactProdInvariant');
15
16var ReactCurrentOwner = require('./ReactCurrentOwner');
17
18var invariant = require('fbjs/lib/invariant');
19var warning = require('fbjs/lib/warning');
20
21function isNative(fn) {
22 // Based on isNative() from Lodash
23 var funcToString = Function.prototype.toString;
24 var hasOwnProperty = Object.prototype.hasOwnProperty;
25 var reIsNative = RegExp('^' + funcToString
26 // Take an example native function source for comparison
27 .call(hasOwnProperty)
28 // Strip regex characters so we can use it for regex
29 .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
30 // Remove hasOwnProperty from the template to make it generic
31 .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
32 try {
33 var source = funcToString.call(fn);
34 return reIsNative.test(source);
35 } catch (err) {
36 return false;
37 }
38}
39
40var canUseCollections =
41// Array.from
42typeof Array.from === 'function' &&
43// Map
44typeof Map === 'function' && isNative(Map) &&
45// Map.prototype.keys
46Map.prototype != null && typeof Map.prototype.keys === 'function' && isNative(Map.prototype.keys) &&
47// Set
48typeof Set === 'function' && isNative(Set) &&
49// Set.prototype.keys
50Set.prototype != null && typeof Set.prototype.keys === 'function' && isNative(Set.prototype.keys);
51
52var setItem;
53var getItem;
54var removeItem;
55var getItemIDs;
56var addRoot;
57var removeRoot;
58var getRootIDs;
59
60if (canUseCollections) {
61 var itemMap = new Map();
62 var rootIDSet = new Set();
63
64 setItem = function (id, item) {
65 itemMap.set(id, item);
66 };
67 getItem = function (id) {
68 return itemMap.get(id);
69 };
70 removeItem = function (id) {
71 itemMap['delete'](id);
72 };
73 getItemIDs = function () {
74 return Array.from(itemMap.keys());
75 };
76
77 addRoot = function (id) {
78 rootIDSet.add(id);
79 };
80 removeRoot = function (id) {
81 rootIDSet['delete'](id);
82 };
83 getRootIDs = function () {
84 return Array.from(rootIDSet.keys());
85 };
86} else {
87 var itemByKey = {};
88 var rootByKey = {};
89
90 // Use non-numeric keys to prevent V8 performance issues:
91 // https://github.com/facebook/react/pull/7232
92 var getKeyFromID = function (id) {
93 return '.' + id;
94 };
95 var getIDFromKey = function (key) {
96 return parseInt(key.substr(1), 10);
97 };
98
99 setItem = function (id, item) {
100 var key = getKeyFromID(id);
101 itemByKey[key] = item;
102 };
103 getItem = function (id) {
104 var key = getKeyFromID(id);
105 return itemByKey[key];
106 };
107 removeItem = function (id) {
108 var key = getKeyFromID(id);
109 delete itemByKey[key];
110 };
111 getItemIDs = function () {
112 return Object.keys(itemByKey).map(getIDFromKey);
113 };
114
115 addRoot = function (id) {
116 var key = getKeyFromID(id);
117 rootByKey[key] = true;
118 };
119 removeRoot = function (id) {
120 var key = getKeyFromID(id);
121 delete rootByKey[key];
122 };
123 getRootIDs = function () {
124 return Object.keys(rootByKey).map(getIDFromKey);
125 };
126}
127
128var unmountedIDs = [];
129
130function purgeDeep(id) {
131 var item = getItem(id);
132 if (item) {
133 var childIDs = item.childIDs;
134
135 removeItem(id);
136 childIDs.forEach(purgeDeep);
137 }
138}
139
140function describeComponentFrame(name, source, ownerName) {
141 return '\n in ' + (name || 'Unknown') + (source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + source.lineNumber + ')' : ownerName ? ' (created by ' + ownerName + ')' : '');
142}
143
144function getDisplayName(element) {
145 if (element == null) {
146 return '#empty';
147 } else if (typeof element === 'string' || typeof element === 'number') {
148 return '#text';
149 } else if (typeof element.type === 'string') {
150 return element.type;
151 } else {
152 return element.type.displayName || element.type.name || 'Unknown';
153 }
154}
155
156function describeID(id) {
157 var name = ReactComponentTreeHook.getDisplayName(id);
158 var element = ReactComponentTreeHook.getElement(id);
159 var ownerID = ReactComponentTreeHook.getOwnerID(id);
160 var ownerName;
161 if (ownerID) {
162 ownerName = ReactComponentTreeHook.getDisplayName(ownerID);
163 }
164 process.env.NODE_ENV !== 'production' ? warning(element, 'ReactComponentTreeHook: Missing React element for debugID %s when ' + 'building stack', id) : void 0;
165 return describeComponentFrame(name, element && element._source, ownerName);
166}
167
168var ReactComponentTreeHook = {
169 onSetChildren: function (id, nextChildIDs) {
170 var item = getItem(id);
171 !item ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Item must have been set') : _prodInvariant('144') : void 0;
172 item.childIDs = nextChildIDs;
173
174 for (var i = 0; i < nextChildIDs.length; i++) {
175 var nextChildID = nextChildIDs[i];
176 var nextChild = getItem(nextChildID);
177 !nextChild ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected hook events to fire for the child before its parent includes it in onSetChildren().') : _prodInvariant('140') : void 0;
178 !(nextChild.childIDs != null || typeof nextChild.element !== 'object' || nextChild.element == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onSetChildren() to fire for a container child before its parent includes it in onSetChildren().') : _prodInvariant('141') : void 0;
179 !nextChild.isMounted ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onMountComponent() to fire for the child before its parent includes it in onSetChildren().') : _prodInvariant('71') : void 0;
180 if (nextChild.parentID == null) {
181 nextChild.parentID = id;
182 // TODO: This shouldn't be necessary but mounting a new root during in
183 // componentWillMount currently causes not-yet-mounted components to
184 // be purged from our tree data so their parent id is missing.
185 }
186 !(nextChild.parentID === id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (%s has parents %s and %s).', nextChildID, nextChild.parentID, id) : _prodInvariant('142', nextChildID, nextChild.parentID, id) : void 0;
187 }
188 },
189 onBeforeMountComponent: function (id, element, parentID) {
190 var item = {
191 element: element,
192 parentID: parentID,
193 text: null,
194 childIDs: [],
195 isMounted: false,
196 updateCount: 0
197 };
198 setItem(id, item);
199 },
200 onBeforeUpdateComponent: function (id, element) {
201 var item = getItem(id);
202 if (!item || !item.isMounted) {
203 // We may end up here as a result of setState() in componentWillUnmount().
204 // In this case, ignore the element.
205 return;
206 }
207 item.element = element;
208 },
209 onMountComponent: function (id) {
210 var item = getItem(id);
211 !item ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Item must have been set') : _prodInvariant('144') : void 0;
212 item.isMounted = true;
213 var isRoot = item.parentID === 0;
214 if (isRoot) {
215 addRoot(id);
216 }
217 },
218 onUpdateComponent: function (id) {
219 var item = getItem(id);
220 if (!item || !item.isMounted) {
221 // We may end up here as a result of setState() in componentWillUnmount().
222 // In this case, ignore the element.
223 return;
224 }
225 item.updateCount++;
226 },
227 onUnmountComponent: function (id) {
228 var item = getItem(id);
229 if (item) {
230 // We need to check if it exists.
231 // `item` might not exist if it is inside an error boundary, and a sibling
232 // error boundary child threw while mounting. Then this instance never
233 // got a chance to mount, but it still gets an unmounting event during
234 // the error boundary cleanup.
235 item.isMounted = false;
236 var isRoot = item.parentID === 0;
237 if (isRoot) {
238 removeRoot(id);
239 }
240 }
241 unmountedIDs.push(id);
242 },
243 purgeUnmountedComponents: function () {
244 if (ReactComponentTreeHook._preventPurging) {
245 // Should only be used for testing.
246 return;
247 }
248
249 for (var i = 0; i < unmountedIDs.length; i++) {
250 var id = unmountedIDs[i];
251 purgeDeep(id);
252 }
253 unmountedIDs.length = 0;
254 },
255 isMounted: function (id) {
256 var item = getItem(id);
257 return item ? item.isMounted : false;
258 },
259 getCurrentStackAddendum: function (topElement) {
260 var info = '';
261 if (topElement) {
262 var name = getDisplayName(topElement);
263 var owner = topElement._owner;
264 info += describeComponentFrame(name, topElement._source, owner && owner.getName());
265 }
266
267 var currentOwner = ReactCurrentOwner.current;
268 var id = currentOwner && currentOwner._debugID;
269
270 info += ReactComponentTreeHook.getStackAddendumByID(id);
271 return info;
272 },
273 getStackAddendumByID: function (id) {
274 var info = '';
275 while (id) {
276 info += describeID(id);
277 id = ReactComponentTreeHook.getParentID(id);
278 }
279 return info;
280 },
281 getChildIDs: function (id) {
282 var item = getItem(id);
283 return item ? item.childIDs : [];
284 },
285 getDisplayName: function (id) {
286 var element = ReactComponentTreeHook.getElement(id);
287 if (!element) {
288 return null;
289 }
290 return getDisplayName(element);
291 },
292 getElement: function (id) {
293 var item = getItem(id);
294 return item ? item.element : null;
295 },
296 getOwnerID: function (id) {
297 var element = ReactComponentTreeHook.getElement(id);
298 if (!element || !element._owner) {
299 return null;
300 }
301 return element._owner._debugID;
302 },
303 getParentID: function (id) {
304 var item = getItem(id);
305 return item ? item.parentID : null;
306 },
307 getSource: function (id) {
308 var item = getItem(id);
309 var element = item ? item.element : null;
310 var source = element != null ? element._source : null;
311 return source;
312 },
313 getText: function (id) {
314 var element = ReactComponentTreeHook.getElement(id);
315 if (typeof element === 'string') {
316 return element;
317 } else if (typeof element === 'number') {
318 return '' + element;
319 } else {
320 return null;
321 }
322 },
323 getUpdateCount: function (id) {
324 var item = getItem(id);
325 return item ? item.updateCount : 0;
326 },
327
328
329 getRootIDs: getRootIDs,
330 getRegisteredIDs: getItemIDs
331};
332
333module.exports = ReactComponentTreeHook;
\No newline at end of file