@@ -564,50 +564,82 @@ export function attach(
564
564
typeof setSuspenseHandler === 'function' &&
565
565
typeof scheduleUpdate === 'function' ;
566
566
567
- // Set of Fibers (IDs) with recently changed number of error/warning messages.
568
- const fibersWithChangedErrorOrWarningCounts : Set < number > = new Set();
567
+ // Tracks Fibers with recently changed number of error/warning messages.
568
+ // These collections store the Fiber rather than the ID,
569
+ // in order to avoid generating an ID for Fibers that never get mounted
570
+ // (due to e.g. Suspense or error boundaries).
571
+ // onErrorOrWarning() adds Fibers and recordPendingErrorsAndWarnings() later clears them.
572
+ const fibersWithChangedErrorOrWarningCounts : Set < Fiber > = new Set();
573
+ const pendingFiberToErrorsMap: Map< Fiber , Map < string , number > > = new Map ( ) ;
574
+ const pendingFiberToWarningsMap : Map < Fiber , Map < string , number > > = new Map ( ) ;
569
575
570
576
// Mapping of fiber IDs to error/warning messages and counts.
571
- const fiberToErrorsMap : Map< number , Map < string , number > > = new Map ( ) ;
572
- const fiberToWarningsMap : Map < number , Map < string , number > > = new Map ( ) ;
577
+ const fiberIDToErrorsMap : Map < number , Map < string , number > > = new Map ( ) ;
578
+ const fiberIDToWarningsMap : Map < number , Map < string , number > > = new Map ( ) ;
573
579
574
580
function clearErrorsAndWarnings ( ) {
575
581
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
576
- for ( const id of fiberToErrorsMap . keys ( ) ) {
577
- fibersWithChangedErrorOrWarningCounts . add ( id ) ;
578
- updateMostRecentlyInspectedElementIfNecessary ( id ) ;
582
+ for ( const id of fiberIDToErrorsMap . keys ( ) ) {
583
+ const fiber = idToArbitraryFiberMap . get ( id ) ;
584
+ if ( fiber != null ) {
585
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
586
+ updateMostRecentlyInspectedElementIfNecessary ( id ) ;
587
+ }
579
588
}
580
589
581
590
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
582
- for ( const id of fiberToWarningsMap . keys ( ) ) {
583
- fibersWithChangedErrorOrWarningCounts . add ( id ) ;
584
- updateMostRecentlyInspectedElementIfNecessary ( id ) ;
591
+ for ( const id of fiberIDToWarningsMap . keys ( ) ) {
592
+ const fiber = idToArbitraryFiberMap . get ( id ) ;
593
+ if ( fiber != null ) {
594
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
595
+ updateMostRecentlyInspectedElementIfNecessary ( id ) ;
596
+ }
585
597
}
586
598
587
- fiberToErrorsMap . clear ( ) ;
588
- fiberToWarningsMap . clear ( ) ;
599
+ fiberIDToErrorsMap . clear ( ) ;
600
+ fiberIDToWarningsMap . clear ( ) ;
589
601
590
602
flushPendingEvents ( ) ;
591
603
}
592
604
593
- function clearErrorsForFiberID ( id : number ) {
594
- if ( fiberToErrorsMap . has ( id ) ) {
595
- fiberToErrorsMap . delete ( id ) ;
596
- fibersWithChangedErrorOrWarningCounts . add ( id ) ;
597
- flushPendingEvents ( ) ;
598
- }
605
+ function clearMessageCountHelper (
606
+ fiberID : number ,
607
+ pendingFiberToMessageCountMap : Map < Fiber , Map < string , number >> ,
608
+ fiberIDToMessageCountMap : Map < number , Map < string , number >> ,
609
+ ) {
610
+ const fiber = idToArbitraryFiberMap . get ( fiberID ) ;
611
+ if ( fiber != null ) {
612
+ // Throw out any pending changes.
613
+ pendingFiberToErrorsMap . delete ( fiber ) ;
599
614
600
- updateMostRecentlyInspectedElementIfNecessary ( id ) ;
601
- }
615
+ if ( fiberIDToMessageCountMap . has ( fiberID ) ) {
616
+ fiberIDToMessageCountMap . delete ( fiberID ) ;
617
+
618
+ // If previous flushed counts have changed, schedule an update too.
619
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
620
+ flushPendingEvents ( ) ;
602
621
603
- function clearWarningsForFiberID ( id : number ) {
604
- if ( fiberToWarningsMap . has ( id ) ) {
605
- fiberToWarningsMap . delete ( id ) ;
606
- fibersWithChangedErrorOrWarningCounts . add ( id ) ;
607
- flushPendingEvents ( ) ;
622
+ updateMostRecentlyInspectedElementIfNecessary ( fiberID ) ;
623
+ } else {
624
+ fibersWithChangedErrorOrWarningCounts . delete ( fiber ) ;
625
+ }
608
626
}
627
+ }
609
628
610
- updateMostRecentlyInspectedElementIfNecessary ( id ) ;
629
+ function clearErrorsForFiberID ( fiberID : number ) {
630
+ clearMessageCountHelper (
631
+ fiberID ,
632
+ pendingFiberToErrorsMap ,
633
+ fiberIDToErrorsMap ,
634
+ ) ;
635
+ }
636
+
637
+ function clearWarningsForFiberID ( fiberID : number ) {
638
+ clearMessageCountHelper (
639
+ fiberID ,
640
+ pendingFiberToWarningsMap ,
641
+ fiberIDToWarningsMap ,
642
+ ) ;
611
643
}
612
644
613
645
function updateMostRecentlyInspectedElementIfNecessary (
@@ -628,36 +660,24 @@ export function attach(
628
660
args : $ReadOnlyArray < any > ,
629
661
) : void {
630
662
const message = format ( ...args ) ;
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 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 = getOrGenerateFiberID ( fiber ) ;
640
-
641
663
if ( __DEBUG__ ) {
642
664
debug ( 'onErrorOrWarning' , fiber , null , `${ type } : "${ message } "` ) ;
643
665
}
644
666
645
667
// Mark this Fiber as needed its warning/error count updated during the next flush.
646
- fibersWithChangedErrorOrWarningCounts . add ( fiberID ) ;
668
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
647
669
648
- // Update the error/warning messages and counts for the Fiber.
649
- const fiberMap = type === 'error' ? fiberToErrorsMap : fiberToWarningsMap ;
650
- const messageMap = fiberMap . get ( fiberID ) ;
670
+ // Track the warning/error for later.
671
+ const fiberMap =
672
+ type === 'error' ? pendingFiberToErrorsMap : pendingFiberToWarningsMap ;
673
+ const messageMap = fiberMap . get ( fiber ) ;
651
674
if ( messageMap != null ) {
652
675
const count = messageMap . get ( message ) || 0 ;
653
676
messageMap . set ( message , count + 1 ) ;
654
677
} else {
655
- fiberMap . set ( fiberID , new Map ( [ [ message , 1 ] ] ) ) ;
678
+ fiberMap . set ( fiber , new Map ( [ [ message , 1 ] ] ) ) ;
656
679
}
657
680
658
- // If this Fiber is currently being inspected, mark it as needing an udpate as well.
659
- updateMostRecentlyInspectedElementIfNecessary ( fiberID ) ;
660
-
661
681
// Passive effects may trigger errors or warnings too;
662
682
// In this case, we should wait until the rest of the passive effects have run,
663
683
// but we shouldn't wait until the next commit because that might be a long time.
@@ -1497,53 +1517,94 @@ export function attach(
1497
1517
1498
1518
function reevaluateErrorsAndWarnings ( ) {
1499
1519
fibersWithChangedErrorOrWarningCounts . clear ( ) ;
1500
- fiberToErrorsMap . forEach ( ( countMap , fiberID ) => {
1501
- fibersWithChangedErrorOrWarningCounts . add ( fiberID ) ;
1520
+ fiberIDToErrorsMap . forEach ( ( countMap , fiberID ) => {
1521
+ const fiber = idToArbitraryFiberMap . get ( fiberID ) ;
1522
+ if ( fiber != null ) {
1523
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
1524
+ }
1502
1525
} ) ;
1503
- fiberToWarningsMap . forEach ( ( countMap , fiberID ) => {
1504
- fibersWithChangedErrorOrWarningCounts . add ( fiberID ) ;
1526
+ fiberIDToWarningsMap . forEach ( ( countMap , fiberID ) => {
1527
+ const fiber = idToArbitraryFiberMap . get ( fiberID ) ;
1528
+ if ( fiber != null ) {
1529
+ fibersWithChangedErrorOrWarningCounts . add ( fiber ) ;
1530
+ }
1505
1531
} ) ;
1506
1532
recordPendingErrorsAndWarnings ( ) ;
1507
1533
}
1508
1534
1509
- function recordPendingErrorsAndWarnings ( ) {
1510
- clearPendingErrorsAndWarningsAfterDelay ( ) ;
1535
+ function mergeMapsAndGetCountHelper (
1536
+ fiber : Fiber ,
1537
+ fiberID : number ,
1538
+ pendingFiberToMessageCountMap : Map < Fiber , Map < string , number >> ,
1539
+ fiberIDToMessageCountMap : Map < number , Map < string , number >> ,
1540
+ ) : number {
1541
+ let newCount = 0 ;
1511
1542
1512
- fibersWithChangedErrorOrWarningCounts . forEach ( fiberID => {
1513
- const fiber = idToArbitraryFiberMap . get ( fiberID ) ;
1514
- if ( fiber != null ) {
1515
- // Don't send updates for Fibers that didn't mount due to e.g. Suspense or an error boundary.
1516
- // We may also need to clean up after ourselves to avoid leaks.
1517
- // See inline comments in onErrorOrWarning() for more info.
1518
- if ( isFiberMountedImpl ( fiber ) !== MOUNTED ) {
1519
- untrackFiberID ( fiber ) ;
1520
- return ;
1521
- }
1543
+ let messageCountMap = fiberIDToMessageCountMap . get ( fiberID ) ;
1522
1544
1523
- let errorCount = 0 ;
1524
- let warningCount = 0 ;
1545
+ const pendingMessageCountMap = pendingFiberToMessageCountMap . get ( fiber ) ;
1546
+ if ( pendingMessageCountMap != null ) {
1547
+ if ( messageCountMap == null ) {
1548
+ messageCountMap = pendingMessageCountMap ;
1525
1549
1526
- if ( ! shouldFilterFiber ( fiber ) ) {
1527
- const errorCountsMap = fiberToErrorsMap . get ( fiberID ) ;
1528
- const warningCountsMap = fiberToWarningsMap . get ( fiberID ) ;
1550
+ fiberIDToMessageCountMap . set ( fiberID , pendingMessageCountMap ) ;
1551
+ } else {
1552
+ // This Flow refinement should not be necessary and yet...
1553
+ const refinedMessageCountMap = ( ( messageCountMap : any ) : Map <
1554
+ string ,
1555
+ number ,
1556
+ > ) ;
1557
+
1558
+ pendingMessageCountMap . forEach ( ( pendingCount , message ) => {
1559
+ const previousCount = refinedMessageCountMap . get ( message ) || 0 ;
1560
+ refinedMessageCountMap . set ( message , previousCount + pendingCount ) ;
1561
+ } ) ;
1562
+ }
1563
+ }
1529
1564
1530
- if ( errorCountsMap != null ) {
1531
- errorCountsMap . forEach ( count => {
1532
- errorCount += count ;
1533
- } ) ;
1534
- }
1535
- if ( warningCountsMap != null ) {
1536
- warningCountsMap . forEach ( count => {
1537
- warningCount += count ;
1538
- } ) ;
1539
- }
1540
- }
1565
+ if ( ! shouldFilterFiber ( fiber ) ) {
1566
+ if ( messageCountMap != null ) {
1567
+ messageCountMap . forEach ( count => {
1568
+ newCount += count ;
1569
+ } ) ;
1570
+ }
1571
+ }
1572
+
1573
+ pendingFiberToMessageCountMap . delete ( fiber ) ;
1574
+
1575
+ return newCount ;
1576
+ }
1577
+
1578
+ function recordPendingErrorsAndWarnings ( ) {
1579
+ clearPendingErrorsAndWarningsAfterDelay ( ) ;
1580
+
1581
+ fibersWithChangedErrorOrWarningCounts . forEach ( fiber => {
1582
+ const fiberID = getFiberIDUnsafe ( fiber ) ;
1583
+ if ( fiberID === null ) {
1584
+ // Don't send updates for Fibers that didn't mount due to e.g. Suspense or an error boundary.
1585
+ } else {
1586
+ const errorCount = mergeMapsAndGetCountHelper (
1587
+ fiber ,
1588
+ fiberID ,
1589
+ pendingFiberToErrorsMap ,
1590
+ fiberIDToErrorsMap ,
1591
+ ) ;
1592
+ const warningCount = mergeMapsAndGetCountHelper (
1593
+ fiber ,
1594
+ fiberID ,
1595
+ pendingFiberToWarningsMap ,
1596
+ fiberIDToWarningsMap ,
1597
+ ) ;
1541
1598
1542
1599
pushOperation ( TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS ) ;
1543
1600
pushOperation ( fiberID ) ;
1544
1601
pushOperation ( errorCount ) ;
1545
1602
pushOperation ( warningCount ) ;
1546
1603
}
1604
+
1605
+ // Always clean up so that we don't leak.
1606
+ pendingFiberToErrorsMap . delete ( fiber ) ;
1607
+ pendingFiberToWarningsMap . delete ( fiber ) ;
1547
1608
} ) ;
1548
1609
fibersWithChangedErrorOrWarningCounts . clear ( ) ;
1549
1610
}
@@ -3012,8 +3073,8 @@ export function attach(
3012
3073
rootType = fiberRoot . _debugRootType ;
3013
3074
}
3014
3075
3015
- const errors = fiberToErrorsMap .get(id) || new Map();
3016
- const warnings = fiberToWarningsMap .get(id) || new Map();
3076
+ const errors = fiberIDToErrorsMap .get(id) || new Map();
3077
+ const warnings = fiberIDToWarningsMap .get(id) || new Map();
3017
3078
3018
3079
return {
3019
3080
id ,
0 commit comments