10
10
import * as React from 'react' ;
11
11
import {
12
12
createContext ,
13
+ unstable_getCacheForType as getCacheForType ,
14
+ unstable_startTransition as startTransition ,
15
+ unstable_useCacheRefresh as useCacheRefresh ,
13
16
useCallback ,
14
17
useContext ,
15
18
useEffect ,
@@ -18,7 +21,6 @@ import {
18
21
useState ,
19
22
} from 'react' ;
20
23
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom' ;
21
- import { createResource } from '../../cache' ;
22
24
import { BridgeContext , StoreContext } from '../context' ;
23
25
import { hydrate , fillInPath } from 'react-devtools-shared/src/hydration' ;
24
26
import { TreeStateContext } from './TreeContext' ;
@@ -33,7 +35,6 @@ import type {
33
35
Element ,
34
36
InspectedElement as InspectedElementFrontend ,
35
37
} from 'react-devtools-shared/src/devtools/views/Components/types' ;
36
- import type { Resource , Thenable } from '../../cache' ;
37
38
38
39
export type StoreAsGlobal = ( id : number , path : Array < string | number > ) => void ;
39
40
@@ -51,13 +52,15 @@ export type GetInspectedElement = (
51
52
id : number ,
52
53
) => InspectedElementFrontend | null ;
53
54
54
- type RefreshInspectedElement = ( ) => void ;
55
+ type ClearErrorsForInspectedElement = ( ) => void ;
56
+ type ClearWarningsForInspectedElement = ( ) => void ;
55
57
56
58
export type InspectedElementContextType = { |
59
+ clearErrorsForInspectedElement : ClearErrorsForInspectedElement ,
60
+ clearWarningsForInspectedElement : ClearWarningsForInspectedElement ,
57
61
copyInspectedElementPath : CopyInspectedElementPath ,
58
62
getInspectedElementPath : GetInspectedElementPath ,
59
63
getInspectedElement : GetInspectedElement ,
60
- refreshInspectedElement : RefreshInspectedElement ,
61
64
storeAsGlobal : StoreAsGlobal ,
62
65
| } ;
63
66
@@ -67,35 +70,21 @@ const InspectedElementContext = createContext<InspectedElementContextType>(
67
70
InspectedElementContext . displayName = 'InspectedElementContext' ;
68
71
69
72
type ResolveFn = ( inspectedElement : InspectedElementFrontend ) => void ;
70
- type InProgressRequest = { |
71
- promise : Thenable < InspectedElementFrontend > ,
72
- resolveFn : ResolveFn ,
73
+ type Callback = ( inspectedElement : InspectedElementFrontend ) => void ;
74
+ type Thenable = { |
75
+ callbacks : Set < Callback > ,
76
+ then : ( callback : Callback ) => void ,
77
+ resolve : ResolveFn ,
73
78
| } ;
74
79
75
- const inProgressRequests : WeakMap < Element , InProgressRequest > = new WeakMap ( ) ;
76
- const resource : Resource <
77
- Element ,
80
+ const inspectedElementThenables : WeakMap < Element , Thenable > = new WeakMap ( ) ;
81
+
82
+ function createInspectedElementCache ( ) : WeakMap <
78
83
Element ,
79
84
InspectedElementFrontend ,
80
- > = createResource (
81
- ( element : Element ) => {
82
- const request = inProgressRequests . get ( element ) ;
83
- if ( request != null ) {
84
- return request . promise ;
85
- }
86
-
87
- let resolveFn = ( ( null : any ) : ResolveFn ) ;
88
- const promise = new Promise ( resolve => {
89
- resolveFn = resolve ;
90
- } ) ;
91
-
92
- inProgressRequests . set ( element , { promise, resolveFn} ) ;
93
-
94
- return promise ;
95
- } ,
96
- ( element : Element ) => element ,
97
- { useWeakMap : true } ,
98
- ) ;
85
+ > {
86
+ return new WeakMap ( ) ;
87
+ }
99
88
100
89
type Props = { |
101
90
children : React$Node ,
@@ -145,28 +134,75 @@ function InspectedElementContextController({children}: Props) {
145
134
[ bridge , store ] ,
146
135
) ;
147
136
137
+ // TODO (cache) Better encapsulate the cache and read/write methods.
138
+ const inspectedElementCache = getCacheForType ( createInspectedElementCache ) ;
139
+
148
140
const getInspectedElement = useCallback < GetInspectedElement > (
149
141
( id : number ) => {
150
142
const element = store . getElementByID ( id ) ;
151
143
if ( element !== null ) {
152
- return resource . read ( element ) ;
153
- } else {
154
- return null ;
144
+ const maybeInspectedElement = inspectedElementCache . get ( element ) ;
145
+ if ( maybeInspectedElement !== undefined ) {
146
+ return maybeInspectedElement ;
147
+ } else {
148
+ const maybeThenable = inspectedElementThenables . get ( element ) ;
149
+ if ( maybeThenable != null ) {
150
+ throw maybeThenable ;
151
+ }
152
+
153
+ const thenable : Thenable = {
154
+ callbacks : new Set ( ) ,
155
+ then : callback => {
156
+ thenable . callbacks . add ( callback ) ;
157
+ } ,
158
+ resolve : inspectedElement => {
159
+ thenable . callbacks . forEach ( callback =>
160
+ callback ( inspectedElement ) ,
161
+ ) ;
162
+ } ,
163
+ } ;
164
+
165
+ inspectedElementThenables . set ( element , thenable ) ;
166
+
167
+ throw thenable ;
168
+ }
155
169
}
170
+ return null ;
156
171
} ,
157
- [ store ] ,
172
+ [ inspectedElementCache , store ] ,
158
173
) ;
159
174
160
175
// It's very important that this context consumes selectedElementID and not inspectedElementID.
161
176
// Otherwise the effect that sends the "inspect" message across the bridge-
162
177
// would itself be blocked by the same render that suspends (waiting for the data).
163
178
const { selectedElementID} = useContext ( TreeStateContext ) ;
164
179
165
- const refreshInspectedElement = useCallback < RefreshInspectedElement > ( ( ) => {
180
+ const refresh = useCacheRefresh ( ) ;
181
+
182
+ const clearErrorsForInspectedElement = useCallback < ClearErrorsForInspectedElement > ( ( ) => {
166
183
if ( selectedElementID !== null ) {
167
184
const rendererID = store . getRendererIDForElement ( selectedElementID ) ;
168
185
if ( rendererID !== null ) {
169
186
bridge . send ( 'inspectElement' , { id : selectedElementID , rendererID} ) ;
187
+
188
+ startTransition ( ( ) => {
189
+ store . clearErrorsForElement ( selectedElementID ) ;
190
+ refresh ( ) ;
191
+ } ) ;
192
+ }
193
+ }
194
+ } , [ bridge , selectedElementID ] ) ;
195
+
196
+ const clearWarningsForInspectedElement = useCallback < ClearWarningsForInspectedElement > ( ( ) => {
197
+ if ( selectedElementID !== null ) {
198
+ const rendererID = store . getRendererIDForElement ( selectedElementID ) ;
199
+ if ( rendererID !== null ) {
200
+ bridge . send ( 'inspectElement' , { id : selectedElementID , rendererID} ) ;
201
+
202
+ startTransition ( ( ) => {
203
+ store . clearWarningsForElement ( selectedElementID ) ;
204
+ refresh ( ) ;
205
+ } ) ;
170
206
}
171
207
}
172
208
} , [ bridge , selectedElementID ] ) ;
@@ -198,7 +234,9 @@ function InspectedElementContextController({children}: Props) {
198
234
199
235
fillInPath ( inspectedElement , data . value , data . path , value ) ;
200
236
201
- resource . write ( element , inspectedElement ) ;
237
+ // TODO (cache) This mutation seems sketchy.
238
+ // Probably need to refresh the cache with a new seed.
239
+ inspectedElementCache . set ( element , inspectedElement ) ;
202
240
203
241
// Schedule update with React if the currently-selected element has been invalidated.
204
242
if ( id === selectedElementID ) {
@@ -277,16 +315,17 @@ function InspectedElementContextController({children}: Props) {
277
315
278
316
element = store . getElementByID ( id ) ;
279
317
if ( element !== null ) {
280
- const request = inProgressRequests . get ( element ) ;
281
- if ( request != null ) {
282
- inProgressRequests . delete ( element ) ;
318
+ inspectedElementCache . set ( element , inspectedElement ) ;
319
+
320
+ const thenable = inspectedElementThenables . get ( element ) ;
321
+ if ( thenable != null ) {
322
+ inspectedElementThenables . delete ( element ) ;
323
+
283
324
batchedUpdates ( ( ) => {
284
- request . resolveFn ( inspectedElement ) ;
325
+ thenable . resolve ( inspectedElement ) ;
285
326
setCurrentlyInspectedElement ( inspectedElement ) ;
286
327
} ) ;
287
328
} else {
288
- resource . write ( element , inspectedElement ) ;
289
-
290
329
// Schedule update with React if the currently-selected element has been invalidated.
291
330
if ( id === selectedElementID ) {
292
331
setCurrentlyInspectedElement ( inspectedElement ) ;
@@ -356,19 +395,21 @@ function InspectedElementContextController({children}: Props) {
356
395
357
396
const value = useMemo (
358
397
( ) => ( {
398
+ clearErrorsForInspectedElement,
399
+ clearWarningsForInspectedElement,
359
400
copyInspectedElementPath,
360
401
getInspectedElement,
361
402
getInspectedElementPath,
362
- refreshInspectedElement,
363
403
storeAsGlobal,
364
404
} ) ,
365
405
// InspectedElement is used to invalidate the cache and schedule an update with React.
366
406
[
407
+ clearErrorsForInspectedElement ,
408
+ clearWarningsForInspectedElement ,
367
409
copyInspectedElementPath ,
368
410
currentlyInspectedElement ,
369
411
getInspectedElement ,
370
412
getInspectedElementPath ,
371
- refreshInspectedElement ,
372
413
storeAsGlobal ,
373
414
] ,
374
415
) ;
0 commit comments