@samitouri / QOS-React-2 / commits / 1e3383a411

DevTools: Reload all roots after Fast Refresh force remount (#21516)

Works around the corrupted Store state by detecting a broken Fast Refresh remount and forcefully dropping the root and re-mounting the entire tree. This prevents Fibers from getting duplicated in the Store (and in the Components tree). The benefit of this approach is that it doesn't rely on an update or change in behavior to Fast Refresh. (This workaround is pretty dirty, but since it's a DEV-only code path, it's probably okay.) Note that this change doesn't fix all of the reported issues (see #21442 (comment)) but it does fix some of them. This commit also slightly refactors the way DevTools assigns and manages unique IDs for Fibers in the backend by removing the indirection of a "primary Fiber" and instead mapping both the primary and alternate. It also removes the previous cache-on-read behavior of getFiberID and splits the method into three separate functions for different use cases: * getOrGenerateFiberID – Like the previous function, this method returns an ID or generates and caches a new one if the Fiber hasn't been seen before. * getFiberIDUnsafe – This function returns an ID if one has already been generated or null if not. (It can be used to e.g. log a message about a Fiber without potentially causing it to leak.) * getFiberIDThrows – This function returns an ID if one has already been generated or it throws. (It can be used to guarantee expected behavior rather than to silently cause a leak.)

Brian Vaughn committed May 18, 2021 at 08:42 UTC 1e3383a41154cb32d8d6b78b2451ee4dabfcb973
4 files changed +692 -345
packages/react-devtools-shared/src/__tests__/FastRefreshDevToolsIntegration-test.js new
+260
@@ -0,0 +1,260 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
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 + * @flow
8 + */
9 +
10 +describe('Fast Refresh', () => {
11 + let React;
12 + let ReactDOM;
13 + let ReactFreshRuntime;
14 + let act;
15 + let babel;
16 + let container;
17 + let exportsObj;
18 + let freshPlugin;
19 + let store;
20 + let withErrorsOrWarningsIgnored;
21 +
22 + afterEach(() => {
23 + jest.resetModules();
24 + });
25 +
26 + beforeEach(() => {
27 + exportsObj = undefined;
28 + container = document.createElement('div');
29 +
30 + babel = require('@babel/core');
31 + freshPlugin = require('react-refresh/babel');
32 +
33 + store = global.store;
34 +
35 + React = require('react');
36 +
37 + ReactFreshRuntime = require('react-refresh/runtime');
38 + ReactFreshRuntime.injectIntoGlobalHook(global);
39 +
40 + ReactDOM = require('react-dom');
41 +
42 + const utils = require('./utils');
43 + act = utils.act;
44 + withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored;
45 + });
46 +
47 + function execute(source) {
48 + const compiled = babel.transform(source, {
49 + babelrc: false,
50 + presets: ['@babel/react'],
51 + plugins: [
52 + [freshPlugin, {skipEnvCheck: true}],
53 + '@babel/plugin-transform-modules-commonjs',
54 + '@babel/plugin-transform-destructuring',
55 + ].filter(Boolean),
56 + }).code;
57 + exportsObj = {};
58 + // eslint-disable-next-line no-new-func
59 + new Function(
60 + 'global',
61 + 'React',
62 + 'exports',
63 + '$RefreshReg$',
64 + '$RefreshSig$',
65 + compiled,
66 + )(global, React, exportsObj, $RefreshReg$, $RefreshSig$);
67 + // Module systems will register exports as a fallback.
68 + // This is useful for cases when e.g. a class is exported,
69 + // and we don't want to propagate the update beyond this module.
70 + $RefreshReg$(exportsObj.default, 'exports.default');
71 + return exportsObj.default;
72 + }
73 +
74 + function render(source) {
75 + const Component = execute(source);
76 + act(() => {
77 + ReactDOM.render(<Component />, container);
78 + });
79 + // Module initialization shouldn't be counted as a hot update.
80 + expect(ReactFreshRuntime.performReactRefresh()).toBe(null);
81 + }
82 +
83 + function patch(source) {
84 + const prevExports = exportsObj;
85 + execute(source);
86 + const nextExports = exportsObj;
87 +
88 + // Check if exported families have changed.
89 + // (In a real module system we'd do this for *all* exports.)
90 + // For example, this can happen if you convert a class to a function.
91 + // Or if you wrap something in a HOC.
92 + const didExportsChange =
93 + ReactFreshRuntime.getFamilyByType(prevExports.default) !==
94 + ReactFreshRuntime.getFamilyByType(nextExports.default);
95 + if (didExportsChange) {
96 + // In a real module system, we would propagate such updates upwards,
97 + // and re-execute modules that imported this one. (Just like if we edited them.)
98 + // This makes adding/removing/renaming exports re-render references to them.
99 + // Here, we'll just force a re-render using the newer type to emulate this.
100 + const NextComponent = nextExports.default;
101 + act(() => {
102 + ReactDOM.render(<NextComponent />, container);
103 + });
104 + }
105 + act(() => {
106 + const result = ReactFreshRuntime.performReactRefresh();
107 + if (!didExportsChange) {
108 + // Normally we expect that some components got updated in our tests.
109 + expect(result).not.toBe(null);
110 + } else {
111 + // However, we have tests where we convert functions to classes,
112 + // and in those cases it's expected nothing would get updated.
113 + // (Instead, the export change branch above would take care of it.)
114 + }
115 + });
116 + expect(ReactFreshRuntime._getMountedRootCount()).toBe(1);
117 + }
118 +
119 + function $RefreshReg$(type, id) {
120 + ReactFreshRuntime.register(type, id);
121 + }
122 +
123 + function $RefreshSig$() {
124 + return ReactFreshRuntime.createSignatureFunctionForTransform();
125 + }
126 +
127 + it('should not break the DevTools store', () => {
128 + render(`
129 + function Parent() {
130 + return <Child key="A" />;
131 + };
132 +
133 + function Child() {
134 + return <div />;
135 + };
136 +
137 + export default Parent;
138 + `);
139 + expect(store).toMatchInlineSnapshot(`
140 + [root]
141 + ▾ <Parent>
142 + <Child key="A">
143 + `);
144 +
145 + let element = container.firstChild;
146 + expect(container.firstChild).not.toBe(null);
147 +
148 + patch(`
149 + function Parent() {
150 + return <Child key="A" />;
151 + };
152 +
153 + function Child() {
154 + return <div />;
155 + };
156 +
157 + export default Parent;
158 + `);
159 + expect(store).toMatchInlineSnapshot(`
160 + [root]
161 + ▾ <Parent>
162 + <Child key="A">
163 + `);
164 +
165 + // State is preserved; this verifies that Fast Refresh is wired up.
166 + expect(container.firstChild).toBe(element);
167 + element = container.firstChild;
168 +
169 + patch(`
170 + function Parent() {
171 + return <Child key="B" />;
172 + };
173 +
174 + function Child() {
175 + return <div />;
176 + };
177 +
178 + export default Parent;
179 + `);
180 + expect(store).toMatchInlineSnapshot(`
181 + [root]
182 + ▾ <Parent>
183 + <Child key="B">
184 + `);
185 +
186 + // State is reset because hooks changed.
187 + expect(container.firstChild).not.toBe(element);
188 + });
189 +
190 + it('should not break when there are warnings in between patching', () => {
191 + withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
192 + render(`
193 + const {useState} = React;
194 +
195 + export default function Component() {
196 + const [state, setState] = useState(1);
197 + console.warn("Expected warning during render");
198 + return null;
199 + }
200 + `);
201 + });
202 + expect(store).toMatchInlineSnapshot(`
203 + ✕ 0, ⚠ 1
204 + [root]
205 + <Component> ⚠
206 + `);
207 +
208 + withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
209 + patch(`
210 + const {useEffect, useState} = React;
211 +
212 + export default function Component() {
213 + const [state, setState] = useState(1);
214 + console.warn("Expected warning during render");
215 + return null;
216 + }
217 + `);
218 + });
219 + expect(store).toMatchInlineSnapshot(`
220 + ✕ 0, ⚠ 2
221 + [root]
222 + <Component> ⚠
223 + `);
224 +
225 + withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
226 + patch(`
227 + const {useEffect, useState} = React;
228 +
229 + export default function Component() {
230 + const [state, setState] = useState(1);
231 + useEffect(() => {});
232 + console.warn("Expected warning during render");
233 + return null;
234 + }
235 + `);
236 + });
237 + expect(store).toMatchInlineSnapshot(`
238 + ✕ 0, ⚠ 1
239 + [root]
240 + <Component> ⚠
241 + `);
242 +
243 + withErrorsOrWarningsIgnored(['Expected warning during render'], () => {
244 + patch(`
245 + const {useEffect, useState} = React;
246 +
247 + export default function Component() {
248 + const [state, setState] = useState(1);
249 + console.warn("Expected warning during render");
250 + return null;
251 + }
252 + `);
253 + });
254 + expect(store).toMatchInlineSnapshot(`
255 + ✕ 0, ⚠ 1
256 + [root]
257 + <Component> ⚠
258 + `);
259 + });
260 +});
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+255 -255
@@ -10,14 +10,14 @@ Object {
10 "props": null,
11 "state": null,
12 },
13 - 3 => Object {
13 + 4 => Object {
14 "context": null,
15 "didHooksChange": false,
16 "isFirstMount": true,
17 "props": null,
18 "state": null,
19 },
20 - 5 => Object {
20 + 6 => Object {
21 "context": null,
22 "didHooksChange": false,
23 "isFirstMount": true,
@@ -30,14 +30,14 @@ Object {
30 "fiberActualDurations": Map {
31 1 => 16,
32 2 => 16,
33 - 3 => 1,
34 - 5 => 1,
33 + 4 => 1,
34 + 6 => 1,
35 },
36 "fiberSelfDurations": Map {
37 1 => 0,
38 2 => 10,
39 - 3 => 1,
40 - 5 => 1,
39 + 4 => 1,
40 + 6 => 1,
41 },
42 "passiveEffectDuration": null,
43 "priorityLevel": "Normal",
@@ -104,7 +104,7 @@ Object {
104 exports[`ProfilingCache should calculate self duration correctly for suspended views: CommitDetails with filtered self durations 2`] = `
105 Object {
106 "changeDescriptions": Map {
107 - 5 => Object {
107 + 7 => Object {
108 "context": null,
109 "didHooksChange": false,
110 "isFirstMount": true,
@@ -115,11 +115,11 @@ Object {
115 "duration": 3,
116 "effectDuration": null,
117 "fiberActualDurations": Map {
118 - 5 => 3,
118 + 7 => 3,
119 3 => 3,
120 },
121 "fiberSelfDurations": Map {
122 - 5 => 3,
122 + 7 => 3,
123 3 => 0,
124 },
125 "passiveEffectDuration": null,
@@ -147,21 +147,21 @@ Object {
147 "props": null,
148 "state": null,
149 },
150 - 3 => Object {
150 + 4 => Object {
151 "context": null,
152 "didHooksChange": false,
153 "isFirstMount": true,
154 "props": null,
155 "state": null,
156 },
157 - 4 => Object {
157 + 5 => Object {
158 "context": null,
159 "didHooksChange": false,
160 "isFirstMount": true,
161 "props": null,
162 "state": null,
163 },
164 - 5 => Object {
164 + 6 => Object {
165 "context": null,
166 "didHooksChange": false,
167 "isFirstMount": true,
@@ -174,16 +174,16 @@ Object {
174 "fiberActualDurations": Map {
175 1 => 12,
176 2 => 12,
177 - 3 => 0,
178 - 4 => 1,
177 + 4 => 0,
178 5 => 1,
179 + 6 => 1,
180 },
181 "fiberSelfDurations": Map {
182 1 => 0,
183 2 => 10,
184 - 3 => 0,
185 - 4 => 1,
184 + 4 => 0,
185 5 => 1,
186 + 6 => 1,
187 },
188 "passiveEffectDuration": null,
189 "priorityLevel": "Normal",
@@ -203,21 +203,21 @@ Object {
203 exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 1 1`] = `
204 Object {
205 "changeDescriptions": Map {
206 - 3 => Object {
206 + 4 => Object {
207 "context": null,
208 "didHooksChange": false,
209 "isFirstMount": false,
210 "props": Array [],
211 "state": null,
212 },
213 - 4 => Object {
213 + 5 => Object {
214 "context": null,
215 "didHooksChange": false,
216 "isFirstMount": false,
217 "props": Array [],
218 "state": null,
219 },
220 - 6 => Object {
220 + 7 => Object {
221 "context": null,
222 "didHooksChange": false,
223 "isFirstMount": true,
@@ -237,16 +237,16 @@ Object {
237 "duration": 13,
238 "effectDuration": null,
239 "fiberActualDurations": Map {
240 - 3 => 0,
241 - 4 => 1,
242 - 6 => 2,
240 + 4 => 0,
241 + 5 => 1,
242 + 7 => 2,
243 2 => 13,
244 1 => 13,
245 },
246 "fiberSelfDurations": Map {
247 - 3 => 0,
248 - 4 => 1,
249 - 6 => 2,
247 + 4 => 0,
248 + 5 => 1,
249 + 7 => 2,
250 2 => 10,
251 1 => 0,
252 },
@@ -268,7 +268,7 @@ Object {
268 exports[`ProfilingCache should collect data for each commit: CommitDetails commitIndex: 2 1`] = `
269 Object {
270 "changeDescriptions": Map {
271 - 3 => Object {
271 + 4 => Object {
272 "context": null,
273 "didHooksChange": false,
274 "isFirstMount": false,
@@ -288,12 +288,12 @@ Object {
288 "duration": 10,
289 "effectDuration": null,
290 "fiberActualDurations": Map {
291 - 3 => 0,
291 + 4 => 0,
292 2 => 10,
293 1 => 10,
294 },
295 "fiberSelfDurations": Map {
296 - 3 => 0,
296 + 4 => 0,
297 2 => 10,
298 1 => 0,
299 },
@@ -368,7 +368,7 @@ Object {
368 },
369 ],
370 Array [
371 - 3,
371 + 4,
372 Object {
373 "context": null,
374 "didHooksChange": false,
@@ -378,7 +378,7 @@ Object {
378 },
379 ],
380 Array [
381 - 4,
381 + 5,
382 Object {
383 "context": null,
384 "didHooksChange": false,
@@ -388,7 +388,7 @@ Object {
388 },
389 ],
390 Array [
391 - 5,
391 + 6,
392 Object {
393 "context": null,
394 "didHooksChange": false,
@@ -410,15 +410,15 @@ Object {
410 12,
411 ],
412 Array [
413 - 3,
413 + 4,
414 0,
415 ],
416 Array [
417 - 4,
417 + 5,
418 1,
419 ],
420 Array [
421 - 5,
421 + 6,
422 1,
423 ],
424 ],
@@ -432,15 +432,15 @@ Object {
432 10,
433 ],
434 Array [
435 - 3,
435 + 4,
436 0,
437 ],
438 Array [
439 - 4,
439 + 5,
440 1,
441 ],
442 Array [
443 - 5,
443 + 6,
444 1,
445 ],
446 ],
@@ -460,7 +460,7 @@ Object {
460 Object {
461 "changeDescriptions": Array [
462 Array [
463 - 3,
463 + 4,
464 Object {
465 "context": null,
466 "didHooksChange": false,
@@ -470,7 +470,7 @@ Object {
470 },
471 ],
472 Array [
473 - 4,
473 + 5,
474 Object {
475 "context": null,
476 "didHooksChange": false,
@@ -480,7 +480,7 @@ Object {
480 },
481 ],
482 Array [
483 - 6,
483 + 7,
484 Object {
485 "context": null,
486 "didHooksChange": false,
@@ -506,15 +506,15 @@ Object {
506 "effectDuration": null,
507 "fiberActualDurations": Array [
508 Array [
509 - 3,
509 + 4,
510 0,
511 ],
512 Array [
513 - 4,
513 + 5,
514 1,
515 ],
516 Array [
517 - 6,
517 + 7,
518 2,
519 ],
520 Array [
@@ -528,15 +528,15 @@ Object {
528 ],
529 "fiberSelfDurations": Array [
530 Array [
531 - 3,
531 + 4,
532 0,
533 ],
534 Array [
535 - 4,
535 + 5,
536 1,
537 ],
538 Array [
539 - 6,
539 + 7,
540 2,
541 ],
542 Array [
@@ -564,7 +564,7 @@ Object {
564 Object {
565 "changeDescriptions": Array [
566 Array [
567 - 3,
567 + 4,
568 Object {
569 "context": null,
570 "didHooksChange": false,
@@ -590,7 +590,7 @@ Object {
590 "effectDuration": null,
591 "fiberActualDurations": Array [
592 Array [
593 - 3,
593 + 4,
594 0,
595 ],
596 Array [
@@ -604,7 +604,7 @@ Object {
604 ],
605 "fiberSelfDurations": Array [
606 Array [
607 - 3,
607 + 4,
608 0,
609 ],
610 Array [
@@ -723,34 +723,34 @@ Object {
723 2,
724 12000,
725 1,
726 - 3,
726 + 4,
727 5,
728 2,
729 2,
730 2,
731 3,
732 4,
733 - 3,
733 + 4,
734 0,
735 1,
736 - 4,
736 + 5,
737 5,
738 2,
739 2,
740 2,
741 4,
742 4,
743 - 4,
743 + 5,
744 1000,
745 1,
746 - 5,
746 + 6,
747 8,
748 2,
749 2,
750 2,
751 0,
752 4,
753 - 5,
753 + 6,
754 1000,
755 ],
756 Array [
@@ -766,14 +766,14 @@ Object {
766 1,
767 50,
768 1,
769 - 6,
769 + 7,
770 5,
771 2,
772 2,
773 1,
774 2,
775 4,
776 - 6,
776 + 7,
777 2000,
778 4,
779 2,
@@ -781,10 +781,10 @@ Object {
781 3,
782 2,
783 4,
784 - 3,
784 4,
786 - 6,
785 5,
786 + 7,
787 + 6,
788 4,
789 1,
790 14000,
@@ -795,16 +795,16 @@ Object {
795 0,
796 2,
797 2,
798 - 6,
799 - 4,
798 + 7,
799 + 5,
800 4,
801 2,
802 11000,
803 3,
804 2,
805 2,
806 - 3,
807 - 5,
806 + 4,
807 + 6,
808 4,
809 1,
810 11000,
@@ -815,7 +815,7 @@ Object {
815 0,
816 2,
817 1,
818 - 3,
818 + 4,
819 ],
820 ],
821 "rootID": 1,
@@ -834,7 +834,7 @@ Array [
834 ]
835 `;
836
837 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 3 1`] = `
837 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
838 Array [
839 0,
840 1,
@@ -842,20 +842,20 @@ Array [
842 ]
843 `;
844
845 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 4 1`] = `
845 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
846 Array [
847 0,
848 ]
849 `;
850
851 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 5 1`] = `
851 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
852 Array [
853 1,
854 2,
855 ]
856 `;
857
858 -exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 6 1`] = `
858 +exports[`ProfilingCache should collect data for each rendered fiber: FiberCommits: element 7 1`] = `
859 Array [
860 2,
861 ]
@@ -879,7 +879,7 @@ Object {
879 },
880 ],
881 Array [
882 - 3,
882 + 4,
883 Object {
884 "context": null,
885 "didHooksChange": false,
@@ -889,7 +889,7 @@ Object {
889 },
890 ],
891 Array [
892 - 4,
892 + 5,
893 Object {
894 "context": null,
895 "didHooksChange": false,
@@ -911,11 +911,11 @@ Object {
911 11,
912 ],
913 Array [
914 - 3,
914 + 4,
915 0,
916 ],
917 Array [
918 - 4,
918 + 5,
919 1,
920 ],
921 ],
@@ -929,11 +929,11 @@ Object {
929 10,
930 ],
931 Array [
932 - 3,
932 + 4,
933 0,
934 ],
935 Array [
936 - 4,
936 + 5,
937 1,
938 ],
939 ],
@@ -953,7 +953,7 @@ Object {
953 Object {
954 "changeDescriptions": Array [
955 Array [
956 - 3,
956 + 4,
957 Object {
958 "context": null,
959 "didHooksChange": false,
@@ -963,7 +963,7 @@ Object {
963 },
964 ],
965 Array [
966 - 5,
966 + 6,
967 Object {
968 "context": null,
969 "didHooksChange": false,
@@ -989,11 +989,11 @@ Object {
989 "effectDuration": null,
990 "fiberActualDurations": Array [
991 Array [
992 - 3,
992 + 4,
993 0,
994 ],
995 Array [
996 - 5,
996 + 6,
997 1,
998 ],
999 Array [
@@ -1007,11 +1007,11 @@ Object {
1007 ],
1008 "fiberSelfDurations": Array [
1009 Array [
1010 - 3,
1010 + 4,
1011 0,
1012 ],
1013 Array [
1014 - 5,
1014 + 6,
1015 1,
1016 ],
1017 Array [
@@ -1039,7 +1039,7 @@ Object {
1039 Object {
1040 "changeDescriptions": Array [
1041 Array [
1042 - 3,
1042 + 4,
1043 Object {
1044 "context": null,
1045 "didHooksChange": false,
@@ -1049,7 +1049,7 @@ Object {
1049 },
1050 ],
1051 Array [
1052 - 5,
1052 + 6,
1053 Object {
1054 "context": null,
1055 "didHooksChange": false,
@@ -1059,7 +1059,7 @@ Object {
1059 },
1060 ],
1061 Array [
1062 - 6,
1062 + 7,
1063 Object {
1064 "context": null,
1065 "didHooksChange": false,
@@ -1085,15 +1085,15 @@ Object {
1085 "effectDuration": null,
1086 "fiberActualDurations": Array [
1087 Array [
1088 - 3,
1088 + 4,
1089 0,
1090 ],
1091 Array [
1092 - 5,
1092 + 6,
1093 1,
1094 ],
1095 Array [
1096 - 6,
1096 + 7,
1097 2,
1098 ],
1099 Array [
@@ -1107,15 +1107,15 @@ Object {
1107 ],
1108 "fiberSelfDurations": Array [
1109 Array [
1110 - 3,
1110 + 4,
1111 0,
1112 ],
1113 Array [
1114 - 5,
1114 + 6,
1115 1,
1116 ],
1117 Array [
1118 - 6,
1118 + 7,
1119 2,
1120 ],
1121 Array [
@@ -1182,24 +1182,24 @@ Object {
1182 2,
1183 11000,
1184 1,
1185 - 3,
1185 + 4,
1186 5,
1187 2,
1188 2,
1189 2,
1190 3,
1191 4,
1192 - 3,
1192 + 4,
1193 0,
1194 1,
1195 - 4,
1195 + 5,
1196 8,
1197 2,
1198 2,
1199 2,
1200 0,
1201 4,
1202 - 4,
1202 + 5,
1203 1000,
1204 ],
1205 Array [
@@ -1215,14 +1215,14 @@ Object {
1215 1,
1216 49,
1217 1,
1218 - 5,
1218 + 6,
1219 5,
1220 2,
1221 2,
1222 1,
1223 2,
1224 4,
1225 - 5,
1225 + 6,
1226 1000,
1227 4,
1228 2,
@@ -1230,9 +1230,9 @@ Object {
1230 3,
1231 2,
1232 3,
1233 - 3,
1234 - 5,
1233 4,
1234 + 6,
1235 + 5,
1236 4,
1237 1,
1238 12000,
@@ -1250,14 +1250,14 @@ Object {
1250 1,
1251 50,
1252 1,
1253 - 6,
1253 + 7,
1254 5,
1255 2,
1256 2,
1257 1,
1258 2,
1259 4,
1260 - 6,
1260 + 7,
1261 2000,
1262 4,
1263 2,
@@ -1265,10 +1265,10 @@ Object {
1265 3,
1266 2,
1267 4,
1268 - 3,
1269 - 5,
1270 - 6,
1268 4,
1269 + 6,
1270 + 7,
1271 + 5,
1272 4,
1273 1,
1274 14000,
@@ -1287,21 +1287,21 @@ Object {
1287 "commitData": Array [
1288 Object {
1289 "changeDescriptions": Map {
1290 - 3 => Object {
1290 + 4 => Object {
1291 "context": null,
1292 "didHooksChange": false,
1293 "isFirstMount": false,
1294 "props": Array [],
1295 "state": null,
1296 },
1297 - 4 => Object {
1297 + 5 => Object {
1298 "context": null,
1299 "didHooksChange": false,
1300 "isFirstMount": false,
1301 "props": Array [],
1302 "state": null,
1303 },
1304 - 10 => Object {
1304 + 12 => Object {
1305 "context": null,
1306 "didHooksChange": false,
1307 "isFirstMount": true,
@@ -1321,16 +1321,16 @@ Object {
1321 "duration": 13,
1322 "effectDuration": null,
1323 "fiberActualDurations": Map {
1324 - 3 => 0,
1325 - 4 => 1,
1326 - 10 => 2,
1324 + 4 => 0,
1325 + 5 => 1,
1326 + 12 => 2,
1327 2 => 13,
1328 1 => 13,
1329 },
1330 "fiberSelfDurations": Map {
1331 - 3 => 0,
1332 - 4 => 1,
1333 - 10 => 2,
1331 + 4 => 0,
1332 + 5 => 1,
1333 + 12 => 2,
1334 2 => 10,
1335 1 => 0,
1336 },
@@ -1349,7 +1349,7 @@ Object {
1349 },
1350 Object {
1351 "changeDescriptions": Map {
1352 - 3 => Object {
1352 + 4 => Object {
1353 "context": null,
1354 "didHooksChange": false,
1355 "isFirstMount": false,
@@ -1369,12 +1369,12 @@ Object {
1369 "duration": 10,
1370 "effectDuration": null,
1371 "fiberActualDurations": Map {
1372 - 3 => 0,
1372 + 4 => 0,
1373 2 => 10,
1374 1 => 10,
1375 },
1376 "fiberSelfDurations": Map {
1377 - 3 => 0,
1377 + 4 => 0,
1378 2 => 10,
1379 1 => 0,
1380 },
@@ -1431,9 +1431,9 @@ Object {
1431 "initialTreeBaseDurations": Map {
1432 1 => 12,
1433 2 => 12,
1434 - 3 => 0,
1435 - 4 => 1,
1434 + 4 => 0,
1435 5 => 1,
1436 + 6 => 1,
1437 },
1438 "operations": Array [
1439 Array [
@@ -1449,14 +1449,14 @@ Object {
1449 1,
1450 50,
1451 1,
1452 - 10,
1452 + 12,
1453 5,
1454 2,
1455 2,
1456 1,
1457 2,
1458 4,
1459 - 10,
1459 + 12,
1460 2000,
1461 4,
1462 2,
@@ -1464,10 +1464,10 @@ Object {
1464 3,
1465 2,
1466 4,
1467 - 3,
1467 4,
1469 - 10,
1468 5,
1469 + 12,
1470 + 6,
1471 4,
1472 1,
1473 14000,
@@ -1478,16 +1478,16 @@ Object {
1478 0,
1479 2,
1480 2,
1481 - 10,
1482 - 4,
1481 + 12,
1482 + 5,
1483 4,
1484 2,
1485 11000,
1486 3,
1487 2,
1488 2,
1489 - 3,
1490 - 5,
1489 + 4,
1490 + 6,
1491 4,
1492 1,
1493 11000,
@@ -1498,7 +1498,7 @@ Object {
1498 0,
1499 2,
1500 1,
1501 - 3,
1501 + 4,
1502 ],
1503 ],
1504 "rootID": 1,
@@ -1515,9 +1515,9 @@ Object {
1515 },
1516 2 => Object {
1517 "children": Array [
1518 - 3,
1518 4,
1519 5,
1520 + 6,
1521 ],
1522 "displayName": "Parent",
1523 "hocDisplayNames": null,
@@ -1525,29 +1525,29 @@ Object {
1525 "key": null,
1526 "type": 5,
1527 },
1528 - 3 => Object {
1528 + 4 => Object {
1529 "children": Array [],
1530 "displayName": "Child",
1531 "hocDisplayNames": null,
1532 - "id": 3,
1532 + "id": 4,
1533 "key": "0",
1534 "type": 5,
1535 },
1536 - 4 => Object {
1536 + 5 => Object {
1537 "children": Array [],
1538 "displayName": "Child",
1539 "hocDisplayNames": null,
1540 - "id": 4,
1540 + "id": 5,
1541 "key": "1",
1542 "type": 5,
1543 },
1544 - 5 => Object {
1544 + 6 => Object {
1545 "children": Array [],
1546 "displayName": "Child",
1547 "hocDisplayNames": Array [
1548 "Memo",
1549 ],
1550 - "id": 5,
1550 + "id": 6,
1551 "key": null,
1552 "type": 8,
1553 },
@@ -1560,21 +1560,21 @@ Object {
1560 "commitData": Array [
1561 Object {
1562 "changeDescriptions": Map {
1563 - 12 => Object {
1563 + 14 => Object {
1564 "context": null,
1565 "didHooksChange": false,
1566 "isFirstMount": true,
1567 "props": null,
1568 "state": null,
1569 },
1570 - 13 => Object {
1570 + 16 => Object {
1571 "context": null,
1572 "didHooksChange": false,
1573 "isFirstMount": true,
1574 "props": null,
1575 "state": null,
1576 },
1577 - 14 => Object {
1577 + 17 => Object {
1578 "context": null,
1579 "didHooksChange": false,
1580 "isFirstMount": true,
@@ -1585,16 +1585,16 @@ Object {
1585 "duration": 11,
1586 "effectDuration": null,
1587 "fiberActualDurations": Map {
1588 - 11 => 11,
1589 - 12 => 11,
1590 - 13 => 0,
1591 - 14 => 1,
1588 + 13 => 11,
1589 + 14 => 11,
1590 + 16 => 0,
1591 + 17 => 1,
1592 },
1593 "fiberSelfDurations": Map {
1594 - 11 => 0,
1595 - 12 => 10,
1594 13 => 0,
1597 - 14 => 1,
1595 + 14 => 10,
1596 + 16 => 0,
1597 + 17 => 1,
1598 },
1599 "passiveEffectDuration": null,
1600 "priorityLevel": "Normal",
@@ -1603,7 +1603,7 @@ Object {
1603 Object {
1604 "displayName": "Anonymous",
1605 "hocDisplayNames": null,
1606 - "id": 11,
1606 + "id": 13,
1607 "key": null,
1608 "type": 11,
1609 },
@@ -1615,7 +1615,7 @@ Object {
1615 "operations": Array [
1616 Array [
1617 1,
1618 - 11,
1618 + 13,
1619 15,
1620 6,
1621 80,
@@ -1633,46 +1633,46 @@ Object {
1633 1,
1634 48,
1635 1,
1636 - 11,
1636 + 13,
1637 11,
1638 1,
1639 1,
1640 4,
1641 - 11,
1641 + 13,
1642 11000,
1643 1,
1644 - 12,
1644 + 14,
1645 5,
1646 - 11,
1646 + 13,
1647 0,
1648 1,
1649 0,
1650 4,
1651 - 12,
1651 + 14,
1652 11000,
1653 1,
1654 - 13,
1654 + 16,
1655 5,
1656 - 12,
1657 - 12,
1656 + 14,
1657 + 14,
1658 2,
1659 3,
1660 4,
1661 - 13,
1661 + 16,
1662 0,
1663 1,
1664 - 14,
1664 + 17,
1665 8,
1666 - 12,
1667 - 12,
1666 + 14,
1667 + 14,
1668 2,
1669 0,
1670 4,
1671 - 14,
1671 + 17,
1672 1000,
1673 ],
1674 ],
1675 - "rootID": 11,
1675 + "rootID": 13,
1676 "snapshots": Map {},
1677 }
1678 `;
@@ -1693,7 +1693,7 @@ Object {
1693 Object {
1694 "displayName": "Anonymous",
1695 "hocDisplayNames": null,
1696 - "id": 6,
1696 + "id": 7,
1697 "key": null,
1698 "type": 11,
1699 },
@@ -1702,62 +1702,62 @@ Object {
1702 ],
1703 "displayName": "Parent",
1704 "initialTreeBaseDurations": Map {
1705 - 6 => 11,
1705 7 => 11,
1707 - 8 => 0,
1708 - 9 => 1,
1706 + 8 => 11,
1707 + 10 => 0,
1708 + 11 => 1,
1709 },
1710 "operations": Array [
1711 Array [
1712 1,
1713 - 6,
1713 + 7,
1714 0,
1715 2,
1716 4,
1717 - 9,
1717 + 11,
1718 + 10,
1719 8,
1720 7,
1720 - 6,
1721 ],
1722 ],
1723 - "rootID": 6,
1723 + "rootID": 7,
1724 "snapshots": Map {
1725 - 6 => Object {
1725 + 7 => Object {
1726 "children": Array [
1727 - 7,
1727 + 8,
1728 ],
1729 "displayName": null,
1730 "hocDisplayNames": null,
1731 - "id": 6,
1731 + "id": 7,
1732 "key": null,
1733 "type": 11,
1734 },
1735 - 7 => Object {
1735 + 8 => Object {
1736 "children": Array [
1737 - 8,
1738 - 9,
1737 + 10,
1738 + 11,
1739 ],
1740 "displayName": "Parent",
1741 "hocDisplayNames": null,
1742 - "id": 7,
1742 + "id": 8,
1743 "key": null,
1744 "type": 5,
1745 },
1746 - 8 => Object {
1746 + 10 => Object {
1747 "children": Array [],
1748 "displayName": "Child",
1749 "hocDisplayNames": null,
1750 - "id": 8,
1750 + "id": 10,
1751 "key": "0",
1752 "type": 5,
1753 },
1754 - 9 => Object {
1754 + 11 => Object {
1755 "children": Array [],
1756 "displayName": "Child",
1757 "hocDisplayNames": Array [
1758 "Memo",
1759 ],
1760 - "id": 9,
1760 + "id": 11,
1761 "key": null,
1762 "type": 8,
1763 },
@@ -1773,7 +1773,7 @@ Object {
1773 Object {
1774 "changeDescriptions": Array [
1775 Array [
1776 - 3,
1776 + 4,
1777 Object {
1778 "context": null,
1779 "didHooksChange": false,
@@ -1783,7 +1783,7 @@ Object {
1783 },
1784 ],
1785 Array [
1786 - 4,
1786 + 5,
1787 Object {
1788 "context": null,
1789 "didHooksChange": false,
@@ -1793,7 +1793,7 @@ Object {
1793 },
1794 ],
1795 Array [
1796 - 10,
1796 + 12,
1797 Object {
1798 "context": null,
1799 "didHooksChange": false,
@@ -1819,15 +1819,15 @@ Object {
1819 "effectDuration": null,
1820 "fiberActualDurations": Array [
1821 Array [
1822 - 3,
1822 + 4,
1823 0,
1824 ],
1825 Array [
1826 - 4,
1826 + 5,
1827 1,
1828 ],
1829 Array [
1830 - 10,
1830 + 12,
1831 2,
1832 ],
1833 Array [
@@ -1841,15 +1841,15 @@ Object {
1841 ],
1842 "fiberSelfDurations": Array [
1843 Array [
1844 - 3,
1844 + 4,
1845 0,
1846 ],
1847 Array [
1848 - 4,
1848 + 5,
1849 1,
1850 ],
1851 Array [
1852 - 10,
1852 + 12,
1853 2,
1854 ],
1855 Array [
@@ -1877,7 +1877,7 @@ Object {
1877 Object {
1878 "changeDescriptions": Array [
1879 Array [
1880 - 3,
1880 + 4,
1881 Object {
1882 "context": null,
1883 "didHooksChange": false,
@@ -1903,7 +1903,7 @@ Object {
1903 "effectDuration": null,
1904 "fiberActualDurations": Array [
1905 Array [
1906 - 3,
1906 + 4,
1907 0,
1908 ],
1909 Array [
@@ -1917,7 +1917,7 @@ Object {
1917 ],
1918 "fiberSelfDurations": Array [
1919 Array [
1920 - 3,
1920 + 4,
1921 0,
1922 ],
1923 Array [
@@ -2004,15 +2004,15 @@ Object {
2004 12,
2005 ],
2006 Array [
2007 - 3,
2007 + 4,
2008 0,
2009 ],
2010 Array [
2011 - 4,
2011 + 5,
2012 1,
2013 ],
2014 Array [
2015 - 5,
2015 + 6,
2016 1,
2017 ],
2018 ],
@@ -2030,14 +2030,14 @@ Object {
2030 1,
2031 50,
2032 1,
2033 - 10,
2033 + 12,
2034 5,
2035 2,
2036 2,
2037 1,
2038 2,
2039 4,
2040 - 10,
2040 + 12,
2041 2000,
2042 4,
2043 2,
@@ -2045,10 +2045,10 @@ Object {
2045 3,
2046 2,
2047 4,
2048 - 3,
2048 4,
2050 - 10,
2049 5,
2050 + 12,
2051 + 6,
2052 4,
2053 1,
2054 14000,
@@ -2059,16 +2059,16 @@ Object {
2059 0,
2060 2,
2061 2,
2062 - 10,
2063 - 4,
2062 + 12,
2063 + 5,
2064 4,
2065 2,
2066 11000,
2067 3,
2068 2,
2069 2,
2070 - 3,
2071 - 5,
2070 + 4,
2071 + 6,
2072 4,
2073 1,
2074 11000,
@@ -2079,7 +2079,7 @@ Object {
2079 0,
2080 2,
2081 1,
2082 - 3,
2082 + 4,
2083 ],
2084 ],
2085 "rootID": 1,
@@ -2101,9 +2101,9 @@ Object {
2101 2,
2102 Object {
2103 "children": Array [
2104 - 3,
2104 4,
2105 5,
2106 + 6,
2107 ],
2108 "displayName": "Parent",
2109 "hocDisplayNames": null,
@@ -2113,36 +2113,36 @@ Object {
2113 },
2114 ],
2115 Array [
2116 - 3,
2116 + 4,
2117 Object {
2118 "children": Array [],
2119 "displayName": "Child",
2120 "hocDisplayNames": null,
2121 - "id": 3,
2121 + "id": 4,
2122 "key": "0",
2123 "type": 5,
2124 },
2125 ],
2126 Array [
2127 - 4,
2127 + 5,
2128 Object {
2129 "children": Array [],
2130 "displayName": "Child",
2131 "hocDisplayNames": null,
2132 - "id": 4,
2132 + "id": 5,
2133 "key": "1",
2134 "type": 5,
2135 },
2136 ],
2137 Array [
2138 - 5,
2138 + 6,
2139 Object {
2140 "children": Array [],
2141 "displayName": "Child",
2142 "hocDisplayNames": Array [
2143 "Memo",
2144 ],
2145 - "id": 5,
2145 + "id": 6,
2146 "key": null,
2147 "type": 8,
2148 },
@@ -2154,7 +2154,7 @@ Object {
2154 Object {
2155 "changeDescriptions": Array [
2156 Array [
2157 - 12,
2157 + 14,
2158 Object {
2159 "context": null,
2160 "didHooksChange": false,
@@ -2164,7 +2164,7 @@ Object {
2164 },
2165 ],
2166 Array [
2167 - 13,
2167 + 16,
2168 Object {
2169 "context": null,
2170 "didHooksChange": false,
@@ -2174,7 +2174,7 @@ Object {
2174 },
2175 ],
2176 Array [
2177 - 14,
2177 + 17,
2178 Object {
2179 "context": null,
2180 "didHooksChange": false,
@@ -2188,37 +2188,37 @@ Object {
2188 "effectDuration": null,
2189 "fiberActualDurations": Array [
2190 Array [
2191 - 11,
2191 + 13,
2192 11,
2193 ],
2194 Array [
2195 - 12,
2195 + 14,
2196 11,
2197 ],
2198 Array [
2199 - 13,
2199 + 16,
2200 0,
2201 ],
2202 Array [
2203 - 14,
2203 + 17,
2204 1,
2205 ],
2206 ],
2207 "fiberSelfDurations": Array [
2208 Array [
2209 - 11,
2209 + 13,
2210 0,
2211 ],
2212 Array [
2213 - 12,
2213 + 14,
2214 10,
2215 ],
2216 Array [
2217 - 13,
2217 + 16,
2218 0,
2219 ],
2220 Array [
2221 - 14,
2221 + 17,
2222 1,
2223 ],
2224 ],
@@ -2229,7 +2229,7 @@ Object {
2229 Object {
2230 "displayName": "Anonymous",
2231 "hocDisplayNames": null,
2232 - "id": 11,
2232 + "id": 13,
2233 "key": null,
2234 "type": 11,
2235 },
@@ -2241,7 +2241,7 @@ Object {
2241 "operations": Array [
2242 Array [
2243 1,
2244 - 11,
2244 + 13,
2245 15,
2246 6,
2247 80,
@@ -2259,46 +2259,46 @@ Object {
2259 1,
2260 48,
2261 1,
2262 - 11,
2262 + 13,
2263 11,
2264 1,
2265 1,
2266 4,
2267 - 11,
2267 + 13,
2268 11000,
2269 1,
2270 - 12,
2270 + 14,
2271 5,
2272 - 11,
2272 + 13,
2273 0,
2274 1,
2275 0,
2276 4,
2277 - 12,
2277 + 14,
2278 11000,
2279 1,
2280 - 13,
2280 + 16,
2281 5,
2282 - 12,
2283 - 12,
2282 + 14,
2283 + 14,
2284 2,
2285 3,
2286 4,
2287 - 13,
2287 + 16,
2288 0,
2289 1,
2290 - 14,
2290 + 17,
2291 8,
2292 - 12,
2293 - 12,
2292 + 14,
2293 + 14,
2294 2,
2295 0,
2296 4,
2297 - 14,
2297 + 17,
2298 1000,
2299 ],
2300 ],
2301 - "rootID": 11,
2301 + "rootID": 13,
2302 "snapshots": Array [],
2303 },
2304 Object {
@@ -2316,7 +2316,7 @@ Object {
2316 Object {
2317 "displayName": "Anonymous",
2318 "hocDisplayNames": null,
2319 - "id": 6,
2319 + "id": 7,
2320 "key": null,
2321 "type": 11,
2322 },
@@ -2326,84 +2326,84 @@ Object {
2326 "displayName": "Parent",
2327 "initialTreeBaseDurations": Array [
2328 Array [
2329 - 6,
2329 + 7,
2330 11,
2331 ],
2332 Array [
2333 - 7,
2333 + 8,
2334 11,
2335 ],
2336 Array [
2337 - 8,
2337 + 10,
2338 0,
2339 ],
2340 Array [
2341 - 9,
2341 + 11,
2342 1,
2343 ],
2344 ],
2345 "operations": Array [
2346 Array [
2347 1,
2348 - 6,
2348 + 7,
2349 0,
2350 2,
2351 4,
2352 - 9,
2352 + 11,
2353 + 10,
2354 8,
2355 7,
2355 - 6,
2356 ],
2357 ],
2358 - "rootID": 6,
2358 + "rootID": 7,
2359 "snapshots": Array [
2360 Array [
2361 - 6,
2361 + 7,
2362 Object {
2363 "children": Array [
2364 - 7,
2364 + 8,
2365 ],
2366 "displayName": null,
2367 "hocDisplayNames": null,
2368 - "id": 6,
2368 + "id": 7,
2369 "key": null,
2370 "type": 11,
2371 },
2372 ],
2373 Array [
2374 - 7,
2374 + 8,
2375 Object {
2376 "children": Array [
2377 - 8,
2378 - 9,
2377 + 10,
2378 + 11,
2379 ],
2380 "displayName": "Parent",
2381 "hocDisplayNames": null,
2382 - "id": 7,
2382 + "id": 8,
2383 "key": null,
2384 "type": 5,
2385 },
2386 ],
2387 Array [
2388 - 8,
2388 + 10,
2389 Object {
2390 "children": Array [],
2391 "displayName": "Child",
2392 "hocDisplayNames": null,
2393 - "id": 8,
2393 + "id": 10,
2394 "key": "0",
2395 "type": 5,
2396 },
2397 ],
2398 Array [
2399 - 9,
2399 + 11,
2400 Object {
2401 "children": Array [],
2402 "displayName": "Child",
2403 "hocDisplayNames": Array [
2404 "Memo",
2405 ],
2406 - "id": 9,
2406 + "id": 11,
2407 "key": null,
2408 "type": 8,
2409 },
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCommitTreeBuilder-test.js.snap
+6 -6
@@ -71,7 +71,7 @@ Object {
71 },
72 3 => Object {
73 "children": Array [
74 - 4,
74 + 6,
75 ],
76 "displayName": "Suspense",
77 "hocDisplayNames": null,
@@ -81,11 +81,11 @@ Object {
81 "treeBaseDuration": 0,
82 "type": 12,
83 },
84 - 4 => Object {
84 + 6 => Object {
85 "children": Array [],
86 "displayName": "LazyInnerComponent",
87 "hocDisplayNames": null,
88 - "id": 4,
88 + "id": 6,
89 "key": null,
90 "parentID": 3,
91 "treeBaseDuration": 0,
@@ -167,7 +167,7 @@ Object {
167 },
168 3 => Object {
169 "children": Array [
170 - 4,
170 + 6,
171 ],
172 "displayName": "Suspense",
173 "hocDisplayNames": null,
@@ -177,11 +177,11 @@ Object {
177 "treeBaseDuration": 0,
178 "type": 12,
179 },
180 - 4 => Object {
180 + 6 => Object {
181 "children": Array [],
182 "displayName": "LazyInnerComponent",
183 "hocDisplayNames": null,
184 - "id": 4,
184 + "id": 6,
185 "key": null,
186 "parentID": 3,
187 "treeBaseDuration": 0,
packages/react-devtools-shared/src/backend/renderer.js
+171 -84
@@ -631,12 +631,16 @@ export function attach(
631
632 // Note that by calling these functions we may be creating the ID for the first time.
633 // If the Fiber is then never mounted, we are responsible for cleaning up after ourselves.
634 - // This is important because getPrimaryFiber() stores a Fiber in the primaryFibers Set.
635 - // If a Fiber never mounts, and we don't clean up after this code, we could leak.
634 + // This is important because getOrGenerateFiberID() stores a Fiber in a couple of local Maps.
635 + // If the Fiber never mounts and we don't clean up after this code, we could leak.
636 // Fortunately we would only leak Fibers that have errors/warnings associated with them,
637 // which is hopefully only a small set and only in DEV mode– but this is still not great.
638 // We should clean up Fibers like this when flushing; see recordPendingErrorsAndWarnings().
639 - const fiberID = getFiberID(getPrimaryFiber(fiber));
639 + const fiberID = getOrGenerateFiberID(fiber);
640 +
641 + if (__DEBUG__) {
642 + debug('onErrorOrWarning', fiber, null, `${type}: "${message}"`);
643 + }
644
645 // Mark this Fiber as needed its warning/error count updated during the next flush.
646 fibersWithChangedErrorOrWarningCounts.add(fiberID);
@@ -702,19 +706,20 @@ export function attach(
706 if (__DEBUG__) {
707 const displayName =
708 fiber.tag + ':' + (getDisplayNameForFiber(fiber) || 'null');
705 - const id = getFiberID(fiber);
709 +
710 + const maybeID = getFiberIDUnsafe(fiber) || '<no id>';
711 const parentDisplayName = parentFiber
712 ? parentFiber.tag +
713 ':' +
714 (getDisplayNameForFiber(parentFiber) || 'null')
715 : '';
711 - const parentID = parentFiber ? getFiberID(parentFiber) : '';
712 - // NOTE: calling getFiberID or getPrimaryFiber is unsafe here
713 - // because it will put them in the map. For now, we'll omit them.
714 - // TODO: better debugging story for this.
716 + const maybeParentID = parentFiber
717 + ? getFiberIDUnsafe(parentFiber) || '<no-id>'
718 + : '';
719 +
720 console.log(
716 - `[renderer] %c${name} %c${displayName} (${id}) %c${
717 - parentFiber ? `${parentDisplayName} (${parentID})` : ''
721 + `[renderer] %c${name} %c${displayName} (${maybeID}) %c${
722 + parentFiber ? `${parentDisplayName} (${maybeParentID})` : ''
723 } %c${extraString}`,
724 'color: red; font-weight: bold;',
725 'color: blue;',
@@ -797,9 +802,15 @@ export function attach(
802 throw Error('Cannot modify filter preferences while profiling');
803 }
804
805 + unmountAndRemountAllRoots(() => {
806 + applyComponentFilters(componentFilters);
807 + });
808 + }
809 +
810 + function unmountAndRemountAllRoots(callback?: Function) {
811 // Recursively unmount all roots.
812 hook.getFiberRoots(rendererID).forEach(root => {
802 - currentRootID = getFiberID(getPrimaryFiber(root.current));
813 + currentRootID = getOrGenerateFiberID(root.current);
814 // The TREE_OPERATION_REMOVE_ROOT operation serves two purposes:
815 // 1. It avoids sending unnecessary bridge traffic to clear a root.
816 // 2. It preserves Fiber IDs when remounting (below) which in turn ID to error/warning mapping.
@@ -808,14 +819,16 @@ export function attach(
819 currentRootID = -1;
820 });
821
811 - applyComponentFilters(componentFilters);
822 + if (typeof callback === 'function') {
823 + callback();
824 + }
825
826 // Reset pseudo counters so that new path selections will be persisted.
827 rootDisplayNameCounter.clear();
828
829 // Recursively re-mount all roots with new filter criteria applied.
830 hook.getFiberRoots(rendererID).forEach(root => {
818 - currentRootID = getFiberID(getPrimaryFiber(root.current));
831 + currentRootID = getOrGenerateFiberID(root.current);
832 setRootPseudoKey(currentRootID, root.current);
833 mountFiberRecursively(root.current, null, false, false);
834 flushPendingEvents(root);
@@ -946,25 +959,16 @@ export function attach(
959 }
960 }
961
949 - // This is a slightly annoying indirection.
950 - // It is currently necessary because DevTools wants to use unique objects as keys for instances.
951 - // However fibers have two versions.
952 - // We use this set to remember first encountered fiber for each conceptual instance.
953 - function getPrimaryFiber(fiber: Fiber): Fiber {
954 - if (primaryFibers.has(fiber)) {
955 - return fiber;
956 - }
957 - const {alternate} = fiber;
958 - if (alternate != null && primaryFibers.has(alternate)) {
959 - return alternate;
960 - }
961 - primaryFibers.add(fiber);
962 - return fiber;
963 - }
964 -
962 + // Map of one or more Fibers in a pair to their unique id number.
963 + // We track both Fibers to support Fast Refresh,
964 + // which may forcefully replace one of the pair as part of hot reloading.
965 + // In that case it's still important to be able to locate the previous ID during subsequent renders.
966 const fiberToIDMap: Map<Fiber, number> = new Map();
966 - const idToFiberMap: Map<number, Fiber> = new Map();
967 - const primaryFibers: Set<Fiber> = new Set();
967 +
968 + // Map of id to one (arbitrary) Fiber in a pair.
969 + // This Map is used to e.g. get the display name for a Fiber or schedule an update,
970 + // operations that should be the same whether the current and work-in-progress Fiber is used.
971 + const idToArbitraryFiberMap: Map<number, Fiber> = new Map();
972
973 // When profiling is supported, we store the latest tree base durations for each Fiber.
974 // This is so that we can quickly capture a snapshot of those values if profiling starts.
@@ -979,13 +983,84 @@ export function attach(
983 // When a mount or update is in progress, this value tracks the root that is being operated on.
984 let currentRootID: number = -1;
985
982 - function getFiberID(primaryFiber: Fiber): number {
983 - if (!fiberToIDMap.has(primaryFiber)) {
984 - const id = getUID();
985 - fiberToIDMap.set(primaryFiber, id);
986 - idToFiberMap.set(id, primaryFiber);
986 + // Returns the unique ID for a Fiber or generates and caches a new one if the Fiber hasn't been seen before.
987 + // Once this method has been called for a Fiber, untrackFiberID() should always be called later to avoid leaking.
988 + function getOrGenerateFiberID(fiber: Fiber): number {
989 + let id = null;
990 + if (fiberToIDMap.has(fiber)) {
991 + id = fiberToIDMap.get(fiber);
992 + } else {
993 + const {alternate} = fiber;
994 + if (alternate !== null && fiberToIDMap.has(alternate)) {
995 + id = fiberToIDMap.get(alternate);
996 + }
997 + }
998 +
999 + if (id === null) {
1000 + id = getUID();
1001 + }
1002 +
1003 + // This refinement is for Flow purposes only.
1004 + const refinedID = ((id: any): number);
1005 +
1006 + // Make sure we're tracking this Fiber
1007 + // e.g. if it just mounted or an error was logged during initial render.
1008 + if (!fiberToIDMap.has(fiber)) {
1009 + fiberToIDMap.set(fiber, refinedID);
1010 + idToArbitraryFiberMap.set(refinedID, fiber);
1011 + }
1012 +
1013 + // Also make sure we're tracking its alternate,
1014 + // e.g. in case this is the first update after mount.
1015 + const {alternate} = fiber;
1016 + if (alternate !== null) {
1017 + if (!fiberToIDMap.has(alternate)) {
1018 + fiberToIDMap.set(alternate, refinedID);
1019 + }
1020 + }
1021 +
1022 + return refinedID;
1023 + }
1024 +
1025 + // Returns an ID if one has already been generated for the Fiber or throws.
1026 + function getFiberIDThrows(fiber: Fiber): number {
1027 + const maybeID = getFiberIDUnsafe(fiber);
1028 + if (maybeID !== null) {
1029 + return maybeID;
1030 + }
1031 + throw Error(
1032 + `Could not find ID for Fiber "${getDisplayNameForFiber(fiber) || ''}"`,
1033 + );
1034 + }
1035 +
1036 + // Returns an ID if one has already been generated for the Fiber or null if one has not been generated.
1037 + // Use this method while e.g. logging to avoid over-retaining Fibers.
1038 + function getFiberIDUnsafe(fiber: Fiber): number | null {
1039 + if (fiberToIDMap.has(fiber)) {
1040 + return ((fiberToIDMap.get(fiber): any): number);
1041 + } else {
1042 + const {alternate} = fiber;
1043 + if (alternate !== null && fiberToIDMap.has(alternate)) {
1044 + return ((fiberToIDMap.get(alternate): any): number);
1045 + }
1046 + }
1047 + return null;
1048 + }
1049 +
1050 + // Removes a Fiber (and its alternate) from the Maps used to track their id.
1051 + // This method should always be called when a Fiber is unmounting.
1052 + function untrackFiberID(fiber: Fiber) {
1053 + const fiberID = getFiberIDUnsafe(fiber);
1054 + if (fiberID !== null) {
1055 + idToArbitraryFiberMap.delete(fiberID);
1056 + }
1057 +
1058 + fiberToIDMap.delete(fiber);
1059 +
1060 + const {alternate} = fiber;
1061 + if (alternate !== null) {
1062 + fiberToIDMap.delete(alternate);
1063 }
988 - return ((fiberToIDMap.get(primaryFiber): any): number);
1064 }
1065
1066 function getChangeDescription(
@@ -1046,7 +1121,7 @@ export function attach(
1121 switch (getElementTypeForFiber(fiber)) {
1122 case ElementTypeClass:
1123 if (idToContextsMap !== null) {
1049 - const id = getFiberID(getPrimaryFiber(fiber));
1124 + const id = getFiberIDThrows(fiber);
1125 const contexts = getContextsForFiber(fiber);
1126 if (contexts !== null) {
1127 idToContextsMap.set(id, contexts);
@@ -1102,7 +1177,7 @@ export function attach(
1177 switch (getElementTypeForFiber(fiber)) {
1178 case ElementTypeClass:
1179 if (idToContextsMap !== null) {
1105 - const id = getFiberID(getPrimaryFiber(fiber));
1180 + const id = getFiberIDThrows(fiber);
1181 const prevContexts = idToContextsMap.has(id)
1182 ? idToContextsMap.get(id)
1183 : null;
@@ -1370,15 +1445,13 @@ export function attach(
1445 clearPendingErrorsAndWarningsAfterDelay();
1446
1447 fibersWithChangedErrorOrWarningCounts.forEach(fiberID => {
1373 - const fiber = idToFiberMap.get(fiberID);
1448 + const fiber = idToArbitraryFiberMap.get(fiberID);
1449 if (fiber != null) {
1450 // Don't send updates for Fibers that didn't mount due to e.g. Suspense or an error boundary.
1451 // We may also need to clean up after ourselves to avoid leaks.
1452 // See inline comments in onErrorOrWarning() for more info.
1453 if (isFiberMountedImpl(fiber) !== MOUNTED) {
1379 - fiberToIDMap.delete(fiber);
1380 - idToFiberMap.delete(fiberID);
1381 - primaryFibers.delete(fiber);
1454 + untrackFiberID(fiber);
1455 return;
1456 }
1457
@@ -1539,7 +1612,7 @@ export function attach(
1612 }
1613
1614 const isRoot = fiber.tag === HostRoot;
1542 - const id = getFiberID(getPrimaryFiber(fiber));
1615 + const id = getOrGenerateFiberID(fiber);
1616
1617 const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
1618 const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
@@ -1562,11 +1635,8 @@ export function attach(
1635 const elementType = getElementTypeForFiber(fiber);
1636 const {_debugOwner} = fiber;
1637
1565 - const ownerID =
1566 - _debugOwner != null ? getFiberID(getPrimaryFiber(_debugOwner)) : 0;
1567 - const parentID = parentFiber
1568 - ? getFiberID(getPrimaryFiber(parentFiber))
1569 - : 0;
1638 + const ownerID = _debugOwner != null ? getFiberIDThrows(_debugOwner) : 0;
1639 + const parentID = parentFiber ? getFiberIDThrows(parentFiber) : 0;
1640
1641 const displayNameStringID = getStringID(displayName);
1642
@@ -1601,6 +1671,20 @@ export function attach(
1671 );
1672 }
1673
1674 + const unsafeID = getFiberIDUnsafe(fiber);
1675 + if (fiber._debugNeedsRemount) {
1676 + if (unsafeID === null) {
1677 + // This inidicates a case we can't recover from:
1678 + // Fast Refresh has force remounted a component in a way that we don't have an id for.
1679 + // We could throw but that's a bad user experience.
1680 + // Or we could ignore the unmount but then Store might end up with a duplicate node.
1681 + // So a fallback is to completely reset the Store.
1682 + // This is costly but since Fast Refresh is only used in DEV builds, it should be okay.
1683 + setTimeout(unmountAndRemountAllRoots, 0);
1684 + return;
1685 + }
1686 + }
1687 +
1688 if (trackedPathMatchFiber !== null) {
1689 // We're in the process of trying to restore previous selection.
1690 // If this fiber matched but is being unmounted, there's no use trying.
@@ -1613,20 +1697,18 @@ export function attach(
1697 }
1698 }
1699
1616 - const isRoot = fiber.tag === HostRoot;
1617 - const primaryFiber = getPrimaryFiber(fiber);
1618 - if (!fiberToIDMap.has(primaryFiber)) {
1700 + if (unsafeID === null) {
1701 // If we've never seen this Fiber, it might be inside of a legacy render Suspense fragment (so the store is not even aware of it).
1702 // In that case we can just ignore it or it will cause errors later on.
1703 // One example of this is a Lazy component that never resolves before being unmounted.
1704 //
1705 // TODO: This is fragile and can obscure actual bugs.
1624 - //
1625 - // Calling getPrimaryFiber() lazily adds fibers to the Map, so clean up after ourselves before returning.
1626 - primaryFibers.delete(primaryFiber);
1706 return;
1707 }
1629 - const id = getFiberID(primaryFiber);
1708 +
1709 + // Flow refinement.
1710 + const id = ((unsafeID: any): number);
1711 + const isRoot = fiber.tag === HostRoot;
1712 if (isRoot) {
1713 // Roots must be removed only after all children (pending and simulated) have been removed.
1714 // So we track it separately.
@@ -1641,14 +1723,15 @@ export function attach(
1723 pendingRealUnmountedIDs.push(id);
1724 }
1725 }
1644 - fiberToIDMap.delete(primaryFiber);
1645 - idToFiberMap.delete(id);
1646 - primaryFibers.delete(primaryFiber);
1726
1648 - const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
1649 - if (isProfilingSupported) {
1650 - idToRootMap.delete(id);
1651 - idToTreeBaseDurationMap.delete(id);
1727 + if (!fiber._debugNeedsRemount) {
1728 + untrackFiberID(fiber);
1729 +
1730 + const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
1731 + if (isProfilingSupported) {
1732 + idToRootMap.delete(id);
1733 + idToTreeBaseDurationMap.delete(id);
1734 + }
1735 }
1736 }
1737
@@ -1675,6 +1758,9 @@ export function attach(
1758 const shouldIncludeInTree = !shouldFilterFiber(fiber);
1759 if (shouldIncludeInTree) {
1760 recordMount(fiber, parentFiber);
1761 + } else {
1762 + // Generate an ID even for filtered Fibers, in case it's needed later (e.g. for Profiling).
1763 + getOrGenerateFiberID(fiber);
1764 }
1765
1766 if (traceUpdatesEnabled) {
@@ -1785,7 +1871,7 @@ export function attach(
1871 }
1872
1873 function recordProfilingDurations(fiber: Fiber) {
1788 - const id = getFiberID(getPrimaryFiber(fiber));
1874 + const id = getFiberIDThrows(fiber);
1875 const {actualDuration, treeBaseDuration} = fiber;
1876
1877 idToTreeBaseDurationMap.set(id, treeBaseDuration || 0);
@@ -1873,7 +1959,7 @@ export function attach(
1959 return;
1960 }
1961 pushOperation(TREE_OPERATION_REORDER_CHILDREN);
1876 - pushOperation(getFiberID(getPrimaryFiber(fiber)));
1962 + pushOperation(getFiberIDThrows(fiber));
1963 pushOperation(numChildren);
1964 for (let i = 0; i < nextChildren.length; i++) {
1965 pushOperation(nextChildren[i]);
@@ -1885,7 +1971,7 @@ export function attach(
1971 nextChildren: Array<number>,
1972 ) {
1973 if (!shouldFilterFiber(fiber)) {
1888 - nextChildren.push(getFiberID(getPrimaryFiber(fiber)));
1974 + nextChildren.push(getFiberIDThrows(fiber));
1975 } else {
1976 let child = fiber.child;
1977 const isTimedOutSuspense =
@@ -1923,6 +2009,8 @@ export function attach(
2009 debug('updateFiberRecursively()', nextFiber, parentFiber);
2010 }
2011
2012 + const id = getOrGenerateFiberID(nextFiber);
2013 +
2014 if (traceUpdatesEnabled) {
2015 const elementType = getElementTypeForFiber(nextFiber);
2016 if (traceNearestHostComponentUpdate) {
@@ -1948,8 +2036,7 @@ export function attach(
2036
2037 if (
2038 mostRecentlyInspectedElement !== null &&
1951 - mostRecentlyInspectedElement.id ===
1952 - getFiberID(getPrimaryFiber(nextFiber)) &&
2039 + mostRecentlyInspectedElement.id === id &&
2040 didFiberRender(prevFiber, nextFiber)
2041 ) {
2042 // If this Fiber has updated, clear cached inspected data.
@@ -2093,7 +2180,7 @@ export function attach(
2180 // we should fall back to recursively marking the nearest host descendants for highlight.
2181 if (traceNearestHostComponentUpdate) {
2182 const hostFibers = findAllCurrentHostFibers(
2096 - getFiberID(getPrimaryFiber(nextFiber)),
2183 + getFiberIDThrows(nextFiber),
2184 );
2185 hostFibers.forEach(hostFiber => {
2186 traceUpdatesForNodes.add(hostFiber.stateNode);
@@ -2177,7 +2264,7 @@ export function attach(
2264 }
2265 // If we have not been profiling, then we can just walk the tree and build up its current state as-is.
2266 hook.getFiberRoots(rendererID).forEach(root => {
2180 - currentRootID = getFiberID(getPrimaryFiber(root.current));
2267 + currentRootID = getOrGenerateFiberID(root.current);
2268 setRootPseudoKey(currentRootID, root.current);
2269
2270 // Handle multi-renderer edge-case where only some v16 renderers support profiling.
@@ -2232,7 +2319,7 @@ export function attach(
2319 const current = root.current;
2320 const alternate = current.alternate;
2321
2235 - currentRootID = getFiberID(getPrimaryFiber(current));
2322 + currentRootID = getOrGenerateFiberID(current);
2323
2324 // Before the traversals, remember to start tracking
2325 // our path in case we have selection to restore.
@@ -2378,7 +2465,7 @@ export function attach(
2465 }
2466
2467 function getDisplayNameForFiberID(id) {
2381 - const fiber = idToFiberMap.get(id);
2468 + const fiber = idToArbitraryFiberMap.get(id);
2469 return fiber != null ? getDisplayNameForFiber(((fiber: any): Fiber)) : null;
2470 }
2471
@@ -2393,7 +2480,7 @@ export function attach(
2480 fiber = fiber.return;
2481 }
2482 }
2396 - return getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
2483 + return getFiberIDThrows(((fiber: any): Fiber));
2484 }
2485 return null;
2486 }
@@ -2463,7 +2550,7 @@ export function attach(
2550 // It would be nice if we updated React to inject this function directly (vs just indirectly via findDOMNode).
2551 // BEGIN copied code
2552 function findCurrentFiberUsingSlowPathById(id: number): Fiber | null {
2466 - const fiber = idToFiberMap.get(id);
2553 + const fiber = idToArbitraryFiberMap.get(id);
2554 if (fiber == null) {
2555 console.warn(`Could not find Fiber with id "${id}"`);
2556 return null;
@@ -2625,7 +2712,7 @@ export function attach(
2712 }
2713
2714 function prepareViewElementSource(id: number): void {
2628 - const fiber = idToFiberMap.get(id);
2715 + const fiber = idToArbitraryFiberMap.get(id);
2716 if (fiber == null) {
2717 console.warn(`Could not find Fiber with id "${id}"`);
2718 return;
@@ -2659,7 +2746,7 @@ export function attach(
2746 function fiberToSerializedElement(fiber: Fiber): SerializedElement {
2747 return {
2748 displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
2662 - id: getFiberID(getPrimaryFiber(fiber)),
2749 + id: getFiberIDThrows(fiber),
2750 key: fiber.key,
2751 type: getElementTypeForFiber(fiber),
2752 };
@@ -2990,7 +3077,7 @@ export function attach(
3077 function updateSelectedElement(inspectedElement: InspectedElement): void {
3078 const {hooks, id, props} = inspectedElement;
3079
2993 - const fiber = idToFiberMap.get(id);
3080 + const fiber = idToArbitraryFiberMap.get(id);
3081 if (fiber == null) {
3082 console.warn(`Could not find Fiber with id "${id}"`);
3083 return;
@@ -3508,7 +3595,7 @@ export function attach(
3595 idToContextsMap = new Map();
3596
3597 hook.getFiberRoots(rendererID).forEach(root => {
3511 - const rootID = getFiberID(getPrimaryFiber(root.current));
3598 + const rootID = getFiberIDThrows(root.current);
3599 ((displayNamesByRootID: any): DisplayNamesByRootID).set(
3600 rootID,
3601 getDisplayNameForRoot(root.current),
@@ -3551,8 +3638,8 @@ export function attach(
3638
3639 const forceFallbackForSuspenseIDs = new Set();
3640 function shouldSuspendFiberAccordingToSet(fiber) {
3554 - const id = getFiberID(getPrimaryFiber(((fiber: any): Fiber)));
3555 - return forceFallbackForSuspenseIDs.has(id);
3641 + const maybeID = getFiberIDUnsafe(((fiber: any): Fiber));
3642 + return maybeID !== null && forceFallbackForSuspenseIDs.has(maybeID);
3643 }
3644
3645 function overrideSuspense(id, forceFallback) {
@@ -3577,7 +3664,7 @@ export function attach(
3664 setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
3665 }
3666 }
3580 - const fiber = idToFiberMap.get(id);
3667 + const fiber = idToArbitraryFiberMap.get(id);
3668 if (fiber != null) {
3669 scheduleUpdate(fiber);
3670 }
@@ -3728,7 +3815,7 @@ export function attach(
3815 case HostRoot:
3816 // Roots don't have a real displayName, index, or key.
3817 // Instead, we'll use the pseudo key (childDisplayName:indexWithThatName).
3731 - const id = getFiberID(getPrimaryFiber(fiber));
3818 + const id = getFiberIDThrows(fiber);
3819 const pseudoKey = rootPseudoKeys.get(id);
3820 if (pseudoKey === undefined) {
3821 throw new Error('Expected mounted root to have known pseudo key.');
@@ -3753,7 +3840,7 @@ export function attach(
3840 // The return path will contain Fibers that are "invisible" to the store
3841 // because their keys and indexes are important to restoring the selection.
3842 function getPathForElement(id: number): Array<PathFrame> | null {
3756 - let fiber = idToFiberMap.get(id);
3843 + let fiber = idToArbitraryFiberMap.get(id);
3844 if (fiber == null) {
3845 return null;
3846 }
@@ -3784,7 +3871,7 @@ export function attach(
3871 return null;
3872 }
3873 return {
3787 - id: getFiberID(getPrimaryFiber(fiber)),
3874 + id: getFiberIDThrows(fiber),
3875 isFullMatch: trackedPathMatchDepth === trackedPath.length - 1,
3876 };
3877 }