@@ -626,6 +626,14 @@ export function attach(
626
626
627
627
// If this Fiber is currently being inspected, mark it as needing an udpate as well.
628
628
updateMostRecentlyInspectedElementIfNecessary ( fiberID ) ;
629
+
630
+ // Passive effects may trigger errors or warnings too;
631
+ // In this case, we should wait until the rest of the passive effects have run,
632
+ // but we shouldn't wait until the next commit because that might be a long time.
633
+ // This would also cause "tearing" between an inspected Component and the tree view.
634
+ // Then again we don't want to flush too soon because this could be an error during async rendering.
635
+ // Use a debounce technique to ensure that we'll eventually flush.
636
+ flushPendingErrorsAndWarningsAfterDelay ( ) ;
629
637
}
630
638
631
639
// Patching the console enables DevTools to do a few useful things:
@@ -1198,10 +1206,12 @@ export function attach(
1198
1206
}
1199
1207
}
1200
1208
1201
- const pendingOperations : Array < number > = [ ] ;
1209
+ type OperationsArray = Array < number > ;
1210
+
1211
+ const pendingOperations : OperationsArray = [ ] ;
1202
1212
const pendingRealUnmountedIDs : Array < number > = [ ] ;
1203
1213
const pendingSimulatedUnmountedIDs : Array < number > = [ ] ;
1204
- let pendingOperationsQueue : Array < Array < number > > | null = [ ] ;
1214
+ let pendingOperationsQueue : Array < OperationsArray > | null = [ ] ;
1205
1215
const pendingStringTable : Map < string , number > = new Map ( ) ;
1206
1216
let pendingStringTableLength : number = 0 ;
1207
1217
let pendingUnmountedRootID : number | null = null ;
@@ -1218,6 +1228,61 @@ export function attach(
1218
1228
pendingOperations . push ( op ) ;
1219
1229
}
1220
1230
1231
+ function flushOrQueueOperations ( operations : OperationsArray ) : void {
1232
+ if ( pendingOperationsQueue !== null ) {
1233
+ pendingOperationsQueue . push ( operations ) ;
1234
+ } else {
1235
+ hook . emit ( 'operations' , operations ) ;
1236
+ }
1237
+ }
1238
+
1239
+ let flushPendingErrorsAndWarningsAfterDelayTimeoutID = null ;
1240
+
1241
+ function clearPendingErrorsAndWarningsAfterDelay ( ) {
1242
+ if ( flushPendingErrorsAndWarningsAfterDelayTimeoutID !== null ) {
1243
+ clearTimeout ( flushPendingErrorsAndWarningsAfterDelayTimeoutID ) ;
1244
+ flushPendingErrorsAndWarningsAfterDelayTimeoutID = null ;
1245
+ }
1246
+ }
1247
+
1248
+ function flushPendingErrorsAndWarningsAfterDelay ( ) {
1249
+ clearPendingErrorsAndWarningsAfterDelay ( ) ;
1250
+
1251
+ flushPendingErrorsAndWarningsAfterDelayTimeoutID = setTimeout ( ( ) => {
1252
+ flushPendingErrorsAndWarningsAfterDelayTimeoutID = null ;
1253
+
1254
+ if ( pendingOperations . length > 0 ) {
1255
+ // On the off chance that something else has pushed pending operations,
1256
+ // we should bail on warnings; it's probably not safe to push midway.
1257
+ return ;
1258
+ }
1259
+
1260
+ recordPendingErrorsAndWarnings ( ) ;
1261
+
1262
+ if ( pendingOperations . length === 0 ) {
1263
+ // No warnings or errors to flush; we can bail out early here too.
1264
+ return ;
1265
+ }
1266
+
1267
+ // We can create a smaller operations array than flushPendingEvents()
1268
+ // because we only need to flush warning and error counts.
1269
+ // Only a few pieces of fixed information are required up front.
1270
+ const operations : OperationsArray = new Array (
1271
+ 3 + pendingOperations . length ,
1272
+ ) ;
1273
+ operations [ 0 ] = rendererID ;
1274
+ operations [ 1 ] = currentRootID ;
1275
+ operations [ 2 ] = 0 ; // String table size
1276
+ for ( let j = 0 ; j < pendingOperations . length ; j ++ ) {
1277
+ operations [ 3 + j ] = pendingOperations [ j ] ;
1278
+ }
1279
+
1280
+ flushOrQueueOperations ( operations ) ;
1281
+
1282
+ pendingOperations . length = 0 ;
1283
+ } , 1000 ) ;
1284
+ }
1285
+
1221
1286
function reevaluateErrorsAndWarnings ( ) {
1222
1287
fibersWithChangedErrorOrWarningCounts . clear ( ) ;
1223
1288
fiberToErrorsMap . forEach ( ( countMap , fiberID ) => {
@@ -1230,6 +1295,8 @@ export function attach(
1230
1295
}
1231
1296
1232
1297
function recordPendingErrorsAndWarnings ( ) {
1298
+ clearPendingErrorsAndWarningsAfterDelay ( ) ;
1299
+
1233
1300
fibersWithChangedErrorOrWarningCounts . forEach ( fiberID => {
1234
1301
const fiber = idToFiberMap . get ( fiberID ) ;
1235
1302
if ( fiber != null ) {
@@ -1319,7 +1386,7 @@ export function attach(
1319
1386
// Which in turn enables fiber props, states, and hooks to be inspected.
1320
1387
let i = 0 ;
1321
1388
operations [ i ++ ] = rendererID ;
1322
- operations [ i ++ ] = currentRootID ; // Use this ID in case the root was unmounted!
1389
+ operations [ i ++ ] = currentRootID ;
1323
1390
1324
1391
// Now fill in the string table.
1325
1392
// [stringTableLength, str1Length, ...str1, str2Length, ...str2, ...]
@@ -1366,18 +1433,9 @@ export function attach(
1366
1433
i += pendingOperations . length ;
1367
1434
1368
1435
// Let the frontend know about tree operations.
1369
- // The first value in this array will identify which root it corresponds to,
1370
- // so we do no longer need to dispatch a separate root-committed event.
1371
- if ( pendingOperationsQueue !== null ) {
1372
- // Until the frontend has been connected, store the tree operations.
1373
- // This will let us avoid walking the tree later when the frontend connects,
1374
- // and it enables the Profiler's reload-and-profile functionality to work as well.
1375
- pendingOperationsQueue . push ( operations ) ;
1376
- } else {
1377
- // If we've already connected to the frontend, just pass the operations through.
1378
- hook . emit ( 'operations' , operations ) ;
1379
- }
1436
+ flushOrQueueOperations ( operations ) ;
1380
1437
1438
+ // Reset all of the pending state now that we've told the frontend about it.
1381
1439
pendingOperations . length = 0 ;
1382
1440
pendingRealUnmountedIDs . length = 0 ;
1383
1441
pendingSimulatedUnmountedIDs . length = 0 ;
0 commit comments