UNPKG

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