@samitouri / QOS-React-2 / commits / 2d48401346

Added context support to change description

Brian Vaughn committed Jun 8, 2019 at 09:10 UTC 2d484013465a543b5ab83292e75f549f2a3085de
7 files changed +787 -56
src/__tests__/__snapshots__/profilingCache-test.js.snap
+544 -31
@@ -94,21 +94,24 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
94 Object {
95 "changeDescriptions": Map {
96 3 => Object {
97 + "context": null,
98 "didHooksChange": false,
99 "props": Array [],
99 - "state": Array [],
100 + "state": null,
101 },
102 4 => Object {
103 + "context": null,
104 "didHooksChange": false,
105 "props": Array [],
104 - "state": Array [],
106 + "state": null,
107 },
108 2 => Object {
109 + "context": null,
110 "didHooksChange": false,
111 "props": Array [
112 "count",
113 ],
111 - "state": Array [],
114 + "state": null,
115 },
116 },
117 "duration": 13,
@@ -137,16 +140,18 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
140 Object {
141 "changeDescriptions": Map {
142 3 => Object {
143 + "context": null,
144 "didHooksChange": false,
145 "props": Array [],
142 - "state": Array [],
146 + "state": null,
147 },
148 2 => Object {
149 + "context": null,
150 "didHooksChange": false,
151 "props": Array [
152 "count",
153 ],
149 - "state": Array [],
154 + "state": null,
155 },
156 },
157 "duration": 10,
@@ -171,11 +176,12 @@ exports[`ProfilingCache should collect data for each commit: CommitDetails commi
176 Object {
177 "changeDescriptions": Map {
178 2 => Object {
179 + "context": null,
180 "didHooksChange": false,
181 "props": Array [
182 "count",
183 ],
178 - "state": Array [],
184 + "state": null,
185 },
186 },
187 "duration": 10,
@@ -256,27 +262,30 @@ Object {
262 Array [
263 3,
264 Object {
265 + "context": null,
266 "didHooksChange": false,
267 "props": Array [],
261 - "state": Array [],
268 + "state": null,
269 },
270 ],
271 Array [
272 4,
273 Object {
274 + "context": null,
275 "didHooksChange": false,
276 "props": Array [],
269 - "state": Array [],
277 + "state": null,
278 },
279 ],
280 Array [
281 2,
282 Object {
283 + "context": null,
284 "didHooksChange": false,
285 "props": Array [
286 "count",
287 ],
279 - "state": Array [],
288 + "state": null,
289 },
290 ],
291 ],
@@ -335,19 +344,21 @@ Object {
344 Array [
345 3,
346 Object {
347 + "context": null,
348 "didHooksChange": false,
349 "props": Array [],
340 - "state": Array [],
350 + "state": null,
351 },
352 ],
353 Array [
354 2,
355 Object {
356 + "context": null,
357 "didHooksChange": false,
358 "props": Array [
359 "count",
360 ],
350 - "state": Array [],
361 + "state": null,
362 },
363 ],
364 ],
@@ -390,11 +401,12 @@ Object {
401 Array [
402 2,
403 Object {
404 + "context": null,
405 "didHooksChange": false,
406 "props": Array [
407 "count",
408 ],
397 - "state": Array [],
409 + "state": null,
410 },
411 ],
412 ],
@@ -662,19 +674,21 @@ Object {
674 Array [
675 3,
676 Object {
677 + "context": null,
678 "didHooksChange": false,
679 "props": Array [],
667 - "state": Array [],
680 + "state": null,
681 },
682 ],
683 Array [
684 2,
685 Object {
686 + "context": null,
687 "didHooksChange": false,
688 "props": Array [
689 "count",
690 ],
677 - "state": Array [],
691 + "state": null,
692 },
693 ],
694 ],
@@ -725,27 +739,30 @@ Object {
739 Array [
740 3,
741 Object {
742 + "context": null,
743 "didHooksChange": false,
744 "props": Array [],
730 - "state": Array [],
745 + "state": null,
746 },
747 ],
748 Array [
749 5,
750 Object {
751 + "context": null,
752 "didHooksChange": false,
753 "props": Array [],
738 - "state": Array [],
754 + "state": null,
755 },
756 ],
757 Array [
758 2,
759 Object {
760 + "context": null,
761 "didHooksChange": false,
762 "props": Array [
763 "count",
764 ],
748 - "state": Array [],
765 + "state": null,
766 },
767 ],
768 ],
@@ -949,21 +966,24 @@ Object {
966 Object {
967 "changeDescriptions": Map {
968 3 => Object {
969 + "context": null,
970 "didHooksChange": false,
971 "props": Array [],
954 - "state": Array [],
972 + "state": null,
973 },
974 4 => Object {
975 + "context": null,
976 "didHooksChange": false,
977 "props": Array [],
959 - "state": Array [],
978 + "state": null,
979 },
980 2 => Object {
981 + "context": null,
982 "didHooksChange": false,
983 "props": Array [
984 "count",
985 ],
966 - "state": Array [],
986 + "state": null,
987 },
988 },
989 "duration": 13,
@@ -989,16 +1009,18 @@ Object {
1009 Object {
1010 "changeDescriptions": Map {
1011 3 => Object {
1012 + "context": null,
1013 "didHooksChange": false,
1014 "props": Array [],
994 - "state": Array [],
1015 + "state": null,
1016 },
1017 2 => Object {
1018 + "context": null,
1019 "didHooksChange": false,
1020 "props": Array [
1021 "count",
1022 ],
1001 - "state": Array [],
1023 + "state": null,
1024 },
1025 },
1026 "duration": 10,
@@ -1020,11 +1042,12 @@ Object {
1042 Object {
1043 "changeDescriptions": Map {
1044 2 => Object {
1045 + "context": null,
1046 "didHooksChange": false,
1047 "props": Array [
1048 "count",
1049 ],
1027 - "state": Array [],
1050 + "state": null,
1051 },
1052 },
1053 "duration": 10,
@@ -1343,27 +1366,30 @@ Object {
1366 Array [
1367 3,
1368 Object {
1369 + "context": null,
1370 "didHooksChange": false,
1371 "props": Array [],
1348 - "state": Array [],
1372 + "state": null,
1373 },
1374 ],
1375 Array [
1376 4,
1377 Object {
1378 + "context": null,
1379 "didHooksChange": false,
1380 "props": Array [],
1356 - "state": Array [],
1381 + "state": null,
1382 },
1383 ],
1384 Array [
1385 2,
1386 Object {
1387 + "context": null,
1388 "didHooksChange": false,
1389 "props": Array [
1390 "count",
1391 ],
1366 - "state": Array [],
1392 + "state": null,
1393 },
1394 ],
1395 ],
@@ -1422,19 +1448,21 @@ Object {
1448 Array [
1449 3,
1450 Object {
1451 + "context": null,
1452 "didHooksChange": false,
1453 "props": Array [],
1427 - "state": Array [],
1454 + "state": null,
1455 },
1456 ],
1457 Array [
1458 2,
1459 Object {
1460 + "context": null,
1461 "didHooksChange": false,
1462 "props": Array [
1463 "count",
1464 ],
1437 - "state": Array [],
1465 + "state": null,
1466 },
1467 ],
1468 ],
@@ -1477,11 +1505,12 @@ Object {
1505 Array [
1506 2,
1507 Object {
1508 + "context": null,
1509 "didHooksChange": false,
1510 "props": Array [
1511 "count",
1512 ],
1484 - "state": Array [],
1513 + "state": null,
1514 },
1515 ],
1516 ],
@@ -1878,6 +1907,488 @@ Object {
1907 }
1908 `;
1909
1910 +exports[`ProfilingCache should record when props/state/hooks change: CommitDetails commitIndex: 0 1`] = `
1911 +Object {
1912 + "changeDescriptions": Map {},
1913 + "duration": 0,
1914 + "fiberActualDurations": Map {
1915 + 1 => 0,
1916 + 2 => 0,
1917 + 3 => 0,
1918 + 4 => 0,
1919 + 5 => 0,
1920 + 6 => 0,
1921 + 7 => 0,
1922 + },
1923 + "fiberSelfDurations": Map {
1924 + 1 => 0,
1925 + 2 => 0,
1926 + 3 => 0,
1927 + 4 => 0,
1928 + 5 => 0,
1929 + 6 => 0,
1930 + 7 => 0,
1931 + },
1932 + "interactionIDs": Array [],
1933 + "priorityLevel": "Immediate",
1934 + "screenshot": null,
1935 + "timestamp": 0,
1936 +}
1937 +`;
1938 +
1939 +exports[`ProfilingCache should record when props/state/hooks change: CommitDetails commitIndex: 1 1`] = `
1940 +Object {
1941 + "changeDescriptions": Map {
1942 + 5 => Object {
1943 + "context": null,
1944 + "didHooksChange": true,
1945 + "props": Array [
1946 + "count",
1947 + ],
1948 + "state": null,
1949 + },
1950 + 4 => Object {
1951 + "context": true,
1952 + "didHooksChange": false,
1953 + "props": Array [],
1954 + "state": null,
1955 + },
1956 + 7 => Object {
1957 + "context": null,
1958 + "didHooksChange": true,
1959 + "props": Array [
1960 + "count",
1961 + ],
1962 + "state": null,
1963 + },
1964 + 6 => Object {
1965 + "context": Array [
1966 + "count",
1967 + ],
1968 + "didHooksChange": false,
1969 + "props": Array [],
1970 + "state": null,
1971 + },
1972 + 2 => Object {
1973 + "context": null,
1974 + "didHooksChange": false,
1975 + "props": Array [],
1976 + "state": Array [
1977 + "count",
1978 + ],
1979 + },
1980 + },
1981 + "duration": 0,
1982 + "fiberActualDurations": Map {
1983 + 5 => 0,
1984 + 4 => 0,
1985 + 7 => 0,
1986 + 6 => 0,
1987 + 3 => 0,
1988 + 2 => 0,
1989 + },
1990 + "fiberSelfDurations": Map {
1991 + 5 => 0,
1992 + 4 => 0,
1993 + 7 => 0,
1994 + 6 => 0,
1995 + 3 => 0,
1996 + 2 => 0,
1997 + },
1998 + "interactionIDs": Array [],
1999 + "priorityLevel": "Immediate",
2000 + "screenshot": null,
2001 + "timestamp": 0,
2002 +}
2003 +`;
2004 +
2005 +exports[`ProfilingCache should record when props/state/hooks change: imported data 1`] = `
2006 +Object {
2007 + "dataForRoots": Array [
2008 + Object {
2009 + "commitData": Array [
2010 + Object {
2011 + "changeDescriptions": Array [],
2012 + "duration": 0,
2013 + "fiberActualDurations": Array [
2014 + Array [
2015 + 1,
2016 + 0,
2017 + ],
2018 + Array [
2019 + 2,
2020 + 0,
2021 + ],
2022 + Array [
2023 + 3,
2024 + 0,
2025 + ],
2026 + Array [
2027 + 4,
2028 + 0,
2029 + ],
2030 + Array [
2031 + 5,
2032 + 0,
2033 + ],
2034 + Array [
2035 + 6,
2036 + 0,
2037 + ],
2038 + Array [
2039 + 7,
2040 + 0,
2041 + ],
2042 + ],
2043 + "fiberSelfDurations": Array [
2044 + Array [
2045 + 1,
2046 + 0,
2047 + ],
2048 + Array [
2049 + 2,
2050 + 0,
2051 + ],
2052 + Array [
2053 + 3,
2054 + 0,
2055 + ],
2056 + Array [
2057 + 4,
2058 + 0,
2059 + ],
2060 + Array [
2061 + 5,
2062 + 0,
2063 + ],
2064 + Array [
2065 + 6,
2066 + 0,
2067 + ],
2068 + Array [
2069 + 7,
2070 + 0,
2071 + ],
2072 + ],
2073 + "interactionIDs": Array [],
2074 + "priorityLevel": "Immediate",
2075 + "screenshot": null,
2076 + "timestamp": 0,
2077 + },
2078 + Object {
2079 + "changeDescriptions": Array [
2080 + Array [
2081 + 5,
2082 + Object {
2083 + "context": null,
2084 + "didHooksChange": true,
2085 + "props": Array [
2086 + "count",
2087 + ],
2088 + "state": null,
2089 + },
2090 + ],
2091 + Array [
2092 + 4,
2093 + Object {
2094 + "context": true,
2095 + "didHooksChange": false,
2096 + "props": Array [],
2097 + "state": null,
2098 + },
2099 + ],
2100 + Array [
2101 + 7,
2102 + Object {
2103 + "context": null,
2104 + "didHooksChange": true,
2105 + "props": Array [
2106 + "count",
2107 + ],
2108 + "state": null,
2109 + },
2110 + ],
2111 + Array [
2112 + 6,
2113 + Object {
2114 + "context": Array [
2115 + "count",
2116 + ],
2117 + "didHooksChange": false,
2118 + "props": Array [],
2119 + "state": null,
2120 + },
2121 + ],
2122 + Array [
2123 + 2,
2124 + Object {
2125 + "context": null,
2126 + "didHooksChange": false,
2127 + "props": Array [],
2128 + "state": Array [
2129 + "count",
2130 + ],
2131 + },
2132 + ],
2133 + ],
2134 + "duration": 0,
2135 + "fiberActualDurations": Array [
2136 + Array [
2137 + 5,
2138 + 0,
2139 + ],
2140 + Array [
2141 + 4,
2142 + 0,
2143 + ],
2144 + Array [
2145 + 7,
2146 + 0,
2147 + ],
2148 + Array [
2149 + 6,
2150 + 0,
2151 + ],
2152 + Array [
2153 + 3,
2154 + 0,
2155 + ],
2156 + Array [
2157 + 2,
2158 + 0,
2159 + ],
2160 + ],
2161 + "fiberSelfDurations": Array [
2162 + Array [
2163 + 5,
2164 + 0,
2165 + ],
2166 + Array [
2167 + 4,
2168 + 0,
2169 + ],
2170 + Array [
2171 + 7,
2172 + 0,
2173 + ],
2174 + Array [
2175 + 6,
2176 + 0,
2177 + ],
2178 + Array [
2179 + 3,
2180 + 0,
2181 + ],
2182 + Array [
2183 + 2,
2184 + 0,
2185 + ],
2186 + ],
2187 + "interactionIDs": Array [],
2188 + "priorityLevel": "Immediate",
2189 + "screenshot": null,
2190 + "timestamp": 0,
2191 + },
2192 + ],
2193 + "displayName": "LegacyContextProvider",
2194 + "initialTreeBaseDurations": Array [],
2195 + "interactionCommits": Array [],
2196 + "interactions": Array [],
2197 + "operations": Array [
2198 + Array [
2199 + 1,
2200 + 1,
2201 + 110,
2202 + 21,
2203 + 76,
2204 + 101,
2205 + 103,
2206 + 97,
2207 + 99,
2208 + 121,
2209 + 67,
2210 + 111,
2211 + 110,
2212 + 116,
2213 + 101,
2214 + 120,
2215 + 116,
2216 + 80,
2217 + 114,
2218 + 111,
2219 + 118,
2220 + 105,
2221 + 100,
2222 + 101,
2223 + 114,
2224 + 16,
2225 + 67,
2226 + 111,
2227 + 110,
2228 + 116,
2229 + 101,
2230 + 120,
2231 + 116,
2232 + 46,
2233 + 80,
2234 + 114,
2235 + 111,
2236 + 118,
2237 + 105,
2238 + 100,
2239 + 101,
2240 + 114,
2241 + 21,
2242 + 77,
2243 + 111,
2244 + 100,
2245 + 101,
2246 + 114,
2247 + 110,
2248 + 67,
2249 + 111,
2250 + 110,
2251 + 116,
2252 + 101,
2253 + 120,
2254 + 116,
2255 + 67,
2256 + 111,
2257 + 110,
2258 + 115,
2259 + 117,
2260 + 109,
2261 + 101,
2262 + 114,
2263 + 26,
2264 + 70,
2265 + 117,
2266 + 110,
2267 + 99,
2268 + 116,
2269 + 105,
2270 + 111,
2271 + 110,
2272 + 67,
2273 + 111,
2274 + 109,
2275 + 112,
2276 + 111,
2277 + 110,
2278 + 101,
2279 + 110,
2280 + 116,
2281 + 87,
2282 + 105,
2283 + 116,
2284 + 104,
2285 + 72,
2286 + 111,
2287 + 111,
2288 + 107,
2289 + 115,
2290 + 21,
2291 + 76,
2292 + 101,
2293 + 103,
2294 + 97,
2295 + 99,
2296 + 121,
2297 + 67,
2298 + 111,
2299 + 110,
2300 + 116,
2301 + 101,
2302 + 120,
2303 + 116,
2304 + 67,
2305 + 111,
2306 + 110,
2307 + 115,
2308 + 117,
2309 + 109,
2310 + 101,
2311 + 114,
2312 + 1,
2313 + 1,
2314 + 11,
2315 + 1,
2316 + 1,
2317 + 1,
2318 + 2,
2319 + 1,
2320 + 1,
2321 + 0,
2322 + 1,
2323 + 0,
2324 + 4,
2325 + 2,
2326 + 0,
2327 + 1,
2328 + 3,
2329 + 2,
2330 + 2,
2331 + 2,
2332 + 2,
2333 + 0,
2334 + 4,
2335 + 3,
2336 + 0,
2337 + 1,
2338 + 4,
2339 + 1,
2340 + 3,
2341 + 2,
2342 + 3,
2343 + 0,
2344 + 4,
2345 + 4,
2346 + 0,
2347 + 1,
2348 + 5,
2349 + 5,
2350 + 4,
2351 + 4,
2352 + 4,
2353 + 0,
2354 + 4,
2355 + 5,
2356 + 0,
2357 + 1,
2358 + 6,
2359 + 1,
2360 + 3,
2361 + 2,
2362 + 5,
2363 + 0,
2364 + 4,
2365 + 6,
2366 + 0,
2367 + 1,
2368 + 7,
2369 + 5,
2370 + 6,
2371 + 6,
2372 + 4,
2373 + 0,
2374 + 4,
2375 + 7,
2376 + 0,
2377 + ],
2378 + Array [
2379 + 1,
2380 + 1,
2381 + 0,
2382 + ],
2383 + ],
2384 + "rootID": 1,
2385 + "snapshots": Array [],
2386 + },
2387 + ],
2388 + "version": 4,
2389 +}
2390 +`;
2391 +
2392 exports[`ProfilingCache should report every traced interaction: Interactions 1`] = `
2393 Array [
2394 Object {
@@ -1951,19 +2462,21 @@ Object {
2462 Array [
2463 3,
2464 Object {
2465 + "context": null,
2466 "didHooksChange": false,
2467 "props": Array [],
1956 - "state": Array [],
2468 + "state": null,
2469 },
2470 ],
2471 Array [
2472 2,
2473 Object {
2474 + "context": null,
2475 "didHooksChange": false,
2476 "props": Array [
2477 "count",
2478 ],
1966 - "state": Array [],
2479 + "state": null,
2480 },
2481 ],
2482 ],
src/__tests__/profilingCache-test.js
+106
@@ -5,6 +5,7 @@ import type Bridge from 'src/bridge';
5 import type Store from 'src/devtools/store';
6
7 describe('ProfilingCache', () => {
8 + let PropTypes;
9 let React;
10 let ReactDOM;
11 let Scheduler;
@@ -23,6 +24,7 @@ describe('ProfilingCache', () => {
24 store.collapseNodesByDefault = false;
25 store.recordChangeDescriptions = true;
26
27 + PropTypes = require('prop-types');
28 React = require('react');
29 ReactDOM = require('react-dom');
30 Scheduler = require('scheduler');
@@ -188,6 +190,110 @@ describe('ProfilingCache', () => {
190 }
191 });
192
193 + it('should record when props/state/hooks change', () => {
194 + let instance = null;
195 +
196 + const ModernContext = React.createContext(0);
197 +
198 + class LegacyContextProvider extends React.Component<
199 + any,
200 + {| count: number |}
201 + > {
202 + static childContextTypes = {
203 + count: PropTypes.number,
204 + };
205 + state = { count: 0 };
206 + getChildContext() {
207 + return this.state;
208 + }
209 + render() {
210 + instance = this;
211 + return (
212 + <ModernContext.Provider value={this.state.count}>
213 + <React.Fragment>
214 + <ModernContextConsumer />
215 + <LegacyContextConsumer />
216 + </React.Fragment>
217 + </ModernContext.Provider>
218 + );
219 + }
220 + }
221 +
222 + const FunctionComponentWithHooks = ({ count }) => {
223 + React.useMemo(() => count, [count]);
224 + return null;
225 + };
226 +
227 + class ModernContextConsumer extends React.Component<any> {
228 + static contextType = ModernContext;
229 + render() {
230 + return <FunctionComponentWithHooks count={this.context} />;
231 + }
232 + }
233 +
234 + class LegacyContextConsumer extends React.Component<any> {
235 + static contextTypes = {
236 + count: PropTypes.number,
237 + };
238 + render() {
239 + return <FunctionComponentWithHooks count={this.context.count} />;
240 + }
241 + }
242 +
243 + const container = document.createElement('div');
244 +
245 + utils.act(() => store.profilerStore.startProfiling());
246 + utils.act(() => ReactDOM.render(<LegacyContextProvider />, container));
247 + expect(instance).not.toBeNull();
248 + utils.act(() => (instance: any).setState({ count: 1 }));
249 + utils.act(() => store.profilerStore.stopProfiling());
250 +
251 + const allCommitData = [];
252 +
253 + function Validator({ commitIndex, previousCommitDetails, rootID }) {
254 + const commitData = store.profilerStore.getCommitData(rootID, commitIndex);
255 + if (previousCommitDetails != null) {
256 + expect(commitData).toEqual(previousCommitDetails);
257 + } else {
258 + allCommitData.push(commitData);
259 + expect(commitData).toMatchSnapshot(
260 + `CommitDetails commitIndex: ${commitIndex}`
261 + );
262 + }
263 + return null;
264 + }
265 +
266 + const rootID = store.roots[0];
267 +
268 + for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
269 + utils.act(() => {
270 + TestRenderer.create(
271 + <Validator
272 + commitIndex={commitIndex}
273 + previousCommitDetails={null}
274 + rootID={rootID}
275 + />
276 + );
277 + });
278 + }
279 +
280 + expect(allCommitData).toHaveLength(2);
281 +
282 + utils.exportImportHelper(bridge, store);
283 +
284 + for (let commitIndex = 0; commitIndex < 2; commitIndex++) {
285 + utils.act(() => {
286 + TestRenderer.create(
287 + <Validator
288 + commitIndex={commitIndex}
289 + previousCommitDetails={allCommitData[commitIndex]}
290 + rootID={rootID}
291 + />
292 + );
293 + });
294 + }
295 + });
296 +
297 it('should calculate a self duration based on actual children (not filtered children)', () => {
298 store.componentFilters = [utils.createDisplayNameFilter('^Parent$')];
299
src/backend/renderer.js
+101 -10
@@ -662,6 +662,7 @@ export function attach(
662 prevFiber.memoizedState,
663 nextFiber.memoizedState
664 ),
665 + context: getContextChangedKeys(nextFiber),
666 props: getChangedKeys(
667 prevFiber.memoizedProps,
668 nextFiber.memoizedProps
@@ -676,6 +677,88 @@ export function attach(
677 }
678 }
679
680 + function updateContextsForFiber(fiber: Fiber) {
681 + switch (getElementTypeForFiber(fiber)) {
682 + case ElementTypeClass:
683 + if (idToContextsMap !== null) {
684 + const id = getFiberID(getPrimaryFiber(fiber));
685 + const contexts = getContextsForFiber(fiber);
686 + if (contexts !== null) {
687 + idToContextsMap.set(id, contexts);
688 + }
689 + }
690 + break;
691 + default:
692 + break;
693 + }
694 + }
695 +
696 + const NO_CONTEXT = {};
697 +
698 + function getContextsForFiber(fiber: Fiber): [Object, any] | null {
699 + switch (getElementTypeForFiber(fiber)) {
700 + case ElementTypeClass:
701 + const instance = fiber.stateNode;
702 + let legacyContext = NO_CONTEXT;
703 + let modernContext = NO_CONTEXT;
704 + if (instance != null) {
705 + if (
706 + instance.constructor &&
707 + instance.constructor.contextType != null
708 + ) {
709 + modernContext = instance.context;
710 + } else {
711 + legacyContext = instance.context;
712 + if (legacyContext && Object.keys(legacyContext).length === 0) {
713 + legacyContext = NO_CONTEXT;
714 + }
715 + }
716 + }
717 + return [legacyContext, modernContext];
718 + default:
719 + return null;
720 + }
721 + }
722 +
723 + function crawlToInitializeContextsMap(fiber: Fiber) {
724 + updateContextsForFiber(fiber);
725 + let current = fiber.child;
726 + while (current !== null) {
727 + crawlToInitializeContextsMap(current);
728 + current = current.sibling;
729 + }
730 + }
731 +
732 + function getContextChangedKeys(fiber: Fiber): null | boolean | Array<string> {
733 + switch (getElementTypeForFiber(fiber)) {
734 + case ElementTypeClass:
735 + if (idToContextsMap !== null) {
736 + const id = getFiberID(getPrimaryFiber(fiber));
737 + const prevContexts = idToContextsMap.has(id)
738 + ? idToContextsMap.get(id)
739 + : null;
740 + const nextContexts = getContextsForFiber(fiber);
741 +
742 + if (prevContexts == null || nextContexts == null) {
743 + return null;
744 + }
745 +
746 + const [prevLegacyContext, prevModernContext] = prevContexts;
747 + const [nextLegacyContext, nextModernContext] = nextContexts;
748 +
749 + if (nextLegacyContext !== NO_CONTEXT) {
750 + return getChangedKeys(prevLegacyContext, nextLegacyContext);
751 + } else if (nextModernContext !== NO_CONTEXT) {
752 + return prevModernContext !== nextModernContext;
753 + }
754 + }
755 + break;
756 + default:
757 + break;
758 + }
759 + return null;
760 + }
761 +
762 function didHooksChange(prev: any, next: any): boolean {
763 if (next == null) {
764 return false;
@@ -701,11 +784,9 @@ export function attach(
784 return false;
785 }
786
704 - function getChangedKeys(prev: any, next: any): Array<string> {
705 - const keys = [];
706 -
707 - if (next == null) {
708 - return keys;
787 + function getChangedKeys(prev: any, next: any): null | Array<string> {
788 + if (prev == null || next == null) {
789 + return null;
790 }
791
792 // We can't report anything meaningful for hooks changes.
@@ -715,16 +796,18 @@ export function attach(
796 next.hasOwnProperty('next') &&
797 next.hasOwnProperty('queue')
798 ) {
718 - return keys;
799 + return null;
800 }
801
721 - // TODO (change descriptions) This does not account for props that were added or removed.
722 - for (let key in prev) {
802 + const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);
803 + const changedKeys = [];
804 + for (let key of keys) {
805 if (prev[key] !== next[key]) {
724 - keys.push(key);
806 + changedKeys.push(key);
807 }
808 }
727 - return keys;
809 +
810 + return changedKeys;
811 }
812
813 // eslint-disable-next-line no-unused-vars
@@ -1139,6 +1222,8 @@ export function attach(
1222 if (changeDescription !== null) {
1223 metadata.changeDescriptions.set(id, changeDescription);
1224 }
1225 +
1226 + updateContextsForFiber(fiber);
1227 }
1228 }
1229 }
@@ -2164,6 +2249,7 @@ export function attach(
2249
2250 let currentCommitProfilingMetadata: CommitProfilingData | null = null;
2251 let displayNamesByRootID: DisplayNamesByRootID | null = null;
2252 + let idToContextsMap: Map<number, any> | null = null;
2253 let initialTreeBaseDurationsMap: Map<number, number> | null = null;
2254 let initialIDToRootMap: Map<number, number> | null = null;
2255 let isProfiling: boolean = false;
@@ -2284,6 +2370,7 @@ export function attach(
2370 displayNamesByRootID = new Map();
2371 initialTreeBaseDurationsMap = new Map(idToTreeBaseDurationMap);
2372 initialIDToRootMap = new Map(idToRootMap);
2373 + idToContextsMap = new Map();
2374
2375 hook.getFiberRoots(rendererID).forEach(root => {
2376 const rootID = getFiberID(getPrimaryFiber(root.current));
@@ -2291,6 +2378,10 @@ export function attach(
2378 rootID,
2379 getDisplayNameForRoot(root.current)
2380 );
2381 +
2382 + if (shouldRecordChangeDescriptions) {
2383 + crawlToInitializeContextsMap(root.current);
2384 + }
2385 });
2386
2387 isProfiling = true;
src/backend/types.js
+4 -3
@@ -112,11 +112,12 @@ export type ReactRenderer = {
112 currentDispatcherRef?: {| current: null | Dispatcher |},
113 };
114
115 -// TODO (change descriptions) Is it important to handle context?
115 +// TODO (change descriptions) Should we report changed hooks keys?
116 export type ChangeDescription = {|
117 + context: Array<string> | boolean | null,
118 didHooksChange: boolean,
118 - props: Array<string>,
119 - state: Array<string>,
119 + props: Array<string> | null,
120 + state: Array<string> | null,
121 |};
122
123 export type CommitDataBackend = {|
src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
+27 -8
@@ -104,21 +104,33 @@ function WhatChanged({
104
105 const changeDescription = changeDescriptions.get(fiberID);
106 if (changeDescription == null) {
107 - return null;
107 + // This indicates that the component mounted during this commit
108 + return (
109 + <div className={styles.Content}>
110 + <label className={styles.Label}>Why did this render?</label>
111 + <div className={styles.WhatChangedItem}>
112 + This is the first time the component rendered.
113 + </div>
114 + </div>
115 + );
116 }
117
118 const changes = [];
119 +
120 if (changeDescription.didHooksChange) {
121 changes.push(
122 <div key="hooks" className={styles.WhatChangedItem}>
114 - • Hooks
123 + • Hooks changed
124 </div>
125 );
126 }
118 - if (changeDescription.props.length !== 0) {
127 + if (
128 + changeDescription.props !== null &&
129 + changeDescription.props.length !== 0
130 + ) {
131 changes.push(
132 <div key="props" className={styles.WhatChangedItem}>
121 - • Props
133 + • Props changed:
134 {changeDescription.props.map(key => (
135 <span key={key} className={styles.WhatChangedKey}>
136 {key}
@@ -127,10 +139,13 @@ function WhatChanged({
139 </div>
140 );
141 }
130 - if (changeDescription.state.length !== 0) {
142 + if (
143 + changeDescription.state !== null &&
144 + changeDescription.state.length !== 0
145 + ) {
146 changes.push(
147 <div key="state" className={styles.WhatChangedItem}>
133 - • State
148 + • State changed:
149 {changeDescription.state.map(key => (
150 <span key={key} className={styles.WhatChangedKey}>
151 {key}
@@ -141,12 +156,16 @@ function WhatChanged({
156 }
157
158 if (changes.length === 0) {
144 - changes.push(<div className={styles.WhatChangedItem}>Nothing</div>);
159 + changes.push(
160 + <div key="nothing" className={styles.WhatChangedItem}>
161 + The parent component rendered.
162 + </div>
163 + );
164 }
165
166 return (
167 <div className={styles.Content}>
149 - <label className={styles.Label}>What changed?</label>
168 + <label className={styles.Label}>Why did this render?</label>
169 {changes}
170 </div>
171 );
src/devtools/views/Profiler/types.js
+4 -3
@@ -31,11 +31,12 @@ export type SnapshotNode = {|
31 type: ElementType,
32 |};
33
34 -// TODO (change descriptions) Is it important to handle context?
34 +// TODO (change descriptions) Should we report changed hooks keys?
35 export type ChangeDescription = {|
36 + context: Array<string> | boolean | null,
37 didHooksChange: boolean,
37 - props: Array<string>,
38 - state: Array<string>,
38 + props: Array<string> | null,
39 + state: Array<string> | null,
40 |};
41
42 export type CommitDataFrontend = {|
src/devtools/views/Settings/Settings.js
+1 -1
@@ -174,7 +174,7 @@ function Settings(_: {||}) {
174 checked={recordChangeDescriptions}
175 onChange={updateRecordChangeDescriptions}
176 />{' '}
177 - Record which props/state/hooks changed while profiling
177 + Record why each component rendered while profiling.
178 </label>
179
180 {store.supportsCaptureScreenshots && (