1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and 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. import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
    
  11. import type {
    
  12.   Fiber,
    
  13.   ContextDependency,
    
  14.   Dependencies,
    
  15. } from './ReactInternalTypes';
    
  16. import type {StackCursor} from './ReactFiberStack';
    
  17. import type {Lanes} from './ReactFiberLane';
    
  18. import type {SharedQueue} from './ReactFiberClassUpdateQueue';
    
  19. import type {TransitionStatus} from './ReactFiberConfig';
    
  20. import type {Hook} from './ReactFiberHooks';
    
  21. 
    
  22. import {isPrimaryRenderer} from './ReactFiberConfig';
    
  23. import {createCursor, push, pop} from './ReactFiberStack';
    
  24. import {
    
  25.   ContextProvider,
    
  26.   ClassComponent,
    
  27.   DehydratedFragment,
    
  28. } from './ReactWorkTags';
    
  29. import {
    
  30.   NoLanes,
    
  31.   isSubsetOfLanes,
    
  32.   includesSomeLane,
    
  33.   mergeLanes,
    
  34.   pickArbitraryLane,
    
  35. } from './ReactFiberLane';
    
  36. import {
    
  37.   NoFlags,
    
  38.   DidPropagateContext,
    
  39.   NeedsPropagation,
    
  40. } from './ReactFiberFlags';
    
  41. 
    
  42. import is from 'shared/objectIs';
    
  43. import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue';
    
  44. import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';
    
  45. import {
    
  46.   enableLazyContextPropagation,
    
  47.   enableServerContext,
    
  48.   enableFormActions,
    
  49.   enableAsyncActions,
    
  50. } from 'shared/ReactFeatureFlags';
    
  51. import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
    
  52. import {
    
  53.   getHostTransitionProvider,
    
  54.   HostTransitionContext,
    
  55. } from './ReactFiberHostContext';
    
  56. 
    
  57. const valueCursor: StackCursor<mixed> = createCursor(null);
    
  58. 
    
  59. let rendererCursorDEV: StackCursor<Object | null>;
    
  60. if (__DEV__) {
    
  61.   rendererCursorDEV = createCursor(null);
    
  62. }
    
  63. let renderer2CursorDEV: StackCursor<Object | null>;
    
  64. if (__DEV__) {
    
  65.   renderer2CursorDEV = createCursor(null);
    
  66. }
    
  67. 
    
  68. let rendererSigil;
    
  69. if (__DEV__) {
    
  70.   // Use this to detect multiple renderers using the same context
    
  71.   rendererSigil = {};
    
  72. }
    
  73. 
    
  74. let currentlyRenderingFiber: Fiber | null = null;
    
  75. let lastContextDependency: ContextDependency<mixed> | null = null;
    
  76. let lastFullyObservedContext: ReactContext<any> | null = null;
    
  77. 
    
  78. let isDisallowedContextReadInDEV: boolean = false;
    
  79. 
    
  80. export function resetContextDependencies(): void {
    
  81.   // This is called right before React yields execution, to ensure `readContext`
    
  82.   // cannot be called outside the render phase.
    
  83.   currentlyRenderingFiber = null;
    
  84.   lastContextDependency = null;
    
  85.   lastFullyObservedContext = null;
    
  86.   if (__DEV__) {
    
  87.     isDisallowedContextReadInDEV = false;
    
  88.   }
    
  89. }
    
  90. 
    
  91. export function enterDisallowedContextReadInDEV(): void {
    
  92.   if (__DEV__) {
    
  93.     isDisallowedContextReadInDEV = true;
    
  94.   }
    
  95. }
    
  96. 
    
  97. export function exitDisallowedContextReadInDEV(): void {
    
  98.   if (__DEV__) {
    
  99.     isDisallowedContextReadInDEV = false;
    
  100.   }
    
  101. }
    
  102. 
    
  103. export function pushProvider<T>(
    
  104.   providerFiber: Fiber,
    
  105.   context: ReactContext<T>,
    
  106.   nextValue: T,
    
  107. ): void {
    
  108.   if (isPrimaryRenderer) {
    
  109.     push(valueCursor, context._currentValue, providerFiber);
    
  110. 
    
  111.     context._currentValue = nextValue;
    
  112.     if (__DEV__) {
    
  113.       push(rendererCursorDEV, context._currentRenderer, providerFiber);
    
  114. 
    
  115.       if (
    
  116.         context._currentRenderer !== undefined &&
    
  117.         context._currentRenderer !== null &&
    
  118.         context._currentRenderer !== rendererSigil
    
  119.       ) {
    
  120.         console.error(
    
  121.           'Detected multiple renderers concurrently rendering the ' +
    
  122.             'same context provider. This is currently unsupported.',
    
  123.         );
    
  124.       }
    
  125.       context._currentRenderer = rendererSigil;
    
  126.     }
    
  127.   } else {
    
  128.     push(valueCursor, context._currentValue2, providerFiber);
    
  129. 
    
  130.     context._currentValue2 = nextValue;
    
  131.     if (__DEV__) {
    
  132.       push(renderer2CursorDEV, context._currentRenderer2, providerFiber);
    
  133. 
    
  134.       if (
    
  135.         context._currentRenderer2 !== undefined &&
    
  136.         context._currentRenderer2 !== null &&
    
  137.         context._currentRenderer2 !== rendererSigil
    
  138.       ) {
    
  139.         console.error(
    
  140.           'Detected multiple renderers concurrently rendering the ' +
    
  141.             'same context provider. This is currently unsupported.',
    
  142.         );
    
  143.       }
    
  144.       context._currentRenderer2 = rendererSigil;
    
  145.     }
    
  146.   }
    
  147. }
    
  148. 
    
  149. export function popProvider(
    
  150.   context: ReactContext<any>,
    
  151.   providerFiber: Fiber,
    
  152. ): void {
    
  153.   const currentValue = valueCursor.current;
    
  154. 
    
  155.   if (isPrimaryRenderer) {
    
  156.     if (
    
  157.       enableServerContext &&
    
  158.       currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
    
  159.     ) {
    
  160.       context._currentValue = context._defaultValue;
    
  161.     } else {
    
  162.       context._currentValue = currentValue;
    
  163.     }
    
  164.     if (__DEV__) {
    
  165.       const currentRenderer = rendererCursorDEV.current;
    
  166.       pop(rendererCursorDEV, providerFiber);
    
  167.       context._currentRenderer = currentRenderer;
    
  168.     }
    
  169.   } else {
    
  170.     if (
    
  171.       enableServerContext &&
    
  172.       currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
    
  173.     ) {
    
  174.       context._currentValue2 = context._defaultValue;
    
  175.     } else {
    
  176.       context._currentValue2 = currentValue;
    
  177.     }
    
  178.     if (__DEV__) {
    
  179.       const currentRenderer2 = renderer2CursorDEV.current;
    
  180.       pop(renderer2CursorDEV, providerFiber);
    
  181.       context._currentRenderer2 = currentRenderer2;
    
  182.     }
    
  183.   }
    
  184. 
    
  185.   pop(valueCursor, providerFiber);
    
  186. }
    
  187. 
    
  188. export function scheduleContextWorkOnParentPath(
    
  189.   parent: Fiber | null,
    
  190.   renderLanes: Lanes,
    
  191.   propagationRoot: Fiber,
    
  192. ) {
    
  193.   // Update the child lanes of all the ancestors, including the alternates.
    
  194.   let node = parent;
    
  195.   while (node !== null) {
    
  196.     const alternate = node.alternate;
    
  197.     if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
    
  198.       node.childLanes = mergeLanes(node.childLanes, renderLanes);
    
  199.       if (alternate !== null) {
    
  200.         alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
    
  201.       }
    
  202.     } else if (
    
  203.       alternate !== null &&
    
  204.       !isSubsetOfLanes(alternate.childLanes, renderLanes)
    
  205.     ) {
    
  206.       alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
    
  207.     } else {
    
  208.       // Neither alternate was updated.
    
  209.       // Normally, this would mean that the rest of the
    
  210.       // ancestor path already has sufficient priority.
    
  211.       // However, this is not necessarily true inside offscreen
    
  212.       // or fallback trees because childLanes may be inconsistent
    
  213.       // with the surroundings. This is why we continue the loop.
    
  214.     }
    
  215.     if (node === propagationRoot) {
    
  216.       break;
    
  217.     }
    
  218.     node = node.return;
    
  219.   }
    
  220.   if (__DEV__) {
    
  221.     if (node !== propagationRoot) {
    
  222.       console.error(
    
  223.         'Expected to find the propagation root when scheduling context work. ' +
    
  224.           'This error is likely caused by a bug in React. Please file an issue.',
    
  225.       );
    
  226.     }
    
  227.   }
    
  228. }
    
  229. 
    
  230. export function propagateContextChange<T>(
    
  231.   workInProgress: Fiber,
    
  232.   context: ReactContext<T>,
    
  233.   renderLanes: Lanes,
    
  234. ): void {
    
  235.   if (enableLazyContextPropagation) {
    
  236.     // TODO: This path is only used by Cache components. Update
    
  237.     // lazilyPropagateParentContextChanges to look for Cache components so they
    
  238.     // can take advantage of lazy propagation.
    
  239.     const forcePropagateEntireTree = true;
    
  240.     propagateContextChanges(
    
  241.       workInProgress,
    
  242.       [context],
    
  243.       renderLanes,
    
  244.       forcePropagateEntireTree,
    
  245.     );
    
  246.   } else {
    
  247.     propagateContextChange_eager(workInProgress, context, renderLanes);
    
  248.   }
    
  249. }
    
  250. 
    
  251. function propagateContextChange_eager<T>(
    
  252.   workInProgress: Fiber,
    
  253.   context: ReactContext<T>,
    
  254.   renderLanes: Lanes,
    
  255. ): void {
    
  256.   // Only used by eager implementation
    
  257.   if (enableLazyContextPropagation) {
    
  258.     return;
    
  259.   }
    
  260.   let fiber = workInProgress.child;
    
  261.   if (fiber !== null) {
    
  262.     // Set the return pointer of the child to the work-in-progress fiber.
    
  263.     fiber.return = workInProgress;
    
  264.   }
    
  265.   while (fiber !== null) {
    
  266.     let nextFiber;
    
  267. 
    
  268.     // Visit this fiber.
    
  269.     const list = fiber.dependencies;
    
  270.     if (list !== null) {
    
  271.       nextFiber = fiber.child;
    
  272. 
    
  273.       let dependency = list.firstContext;
    
  274.       while (dependency !== null) {
    
  275.         // Check if the context matches.
    
  276.         if (dependency.context === context) {
    
  277.           // Match! Schedule an update on this fiber.
    
  278.           if (fiber.tag === ClassComponent) {
    
  279.             // Schedule a force update on the work-in-progress.
    
  280.             const lane = pickArbitraryLane(renderLanes);
    
  281.             const update = createUpdate(lane);
    
  282.             update.tag = ForceUpdate;
    
  283.             // TODO: Because we don't have a work-in-progress, this will add the
    
  284.             // update to the current fiber, too, which means it will persist even if
    
  285.             // this render is thrown away. Since it's a race condition, not sure it's
    
  286.             // worth fixing.
    
  287. 
    
  288.             // Inlined `enqueueUpdate` to remove interleaved update check
    
  289.             const updateQueue = fiber.updateQueue;
    
  290.             if (updateQueue === null) {
    
  291.               // Only occurs if the fiber has been unmounted.
    
  292.             } else {
    
  293.               const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
    
  294.               const pending = sharedQueue.pending;
    
  295.               if (pending === null) {
    
  296.                 // This is the first update. Create a circular list.
    
  297.                 update.next = update;
    
  298.               } else {
    
  299.                 update.next = pending.next;
    
  300.                 pending.next = update;
    
  301.               }
    
  302.               sharedQueue.pending = update;
    
  303.             }
    
  304.           }
    
  305. 
    
  306.           fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
    
  307.           const alternate = fiber.alternate;
    
  308.           if (alternate !== null) {
    
  309.             alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
    
  310.           }
    
  311.           scheduleContextWorkOnParentPath(
    
  312.             fiber.return,
    
  313.             renderLanes,
    
  314.             workInProgress,
    
  315.           );
    
  316. 
    
  317.           // Mark the updated lanes on the list, too.
    
  318.           list.lanes = mergeLanes(list.lanes, renderLanes);
    
  319. 
    
  320.           // Since we already found a match, we can stop traversing the
    
  321.           // dependency list.
    
  322.           break;
    
  323.         }
    
  324.         dependency = dependency.next;
    
  325.       }
    
  326.     } else if (fiber.tag === ContextProvider) {
    
  327.       // Don't scan deeper if this is a matching provider
    
  328.       nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
    
  329.     } else if (fiber.tag === DehydratedFragment) {
    
  330.       // If a dehydrated suspense boundary is in this subtree, we don't know
    
  331.       // if it will have any context consumers in it. The best we can do is
    
  332.       // mark it as having updates.
    
  333.       const parentSuspense = fiber.return;
    
  334. 
    
  335.       if (parentSuspense === null) {
    
  336.         throw new Error(
    
  337.           'We just came from a parent so we must have had a parent. This is a bug in React.',
    
  338.         );
    
  339.       }
    
  340. 
    
  341.       parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
    
  342.       const alternate = parentSuspense.alternate;
    
  343.       if (alternate !== null) {
    
  344.         alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
    
  345.       }
    
  346.       // This is intentionally passing this fiber as the parent
    
  347.       // because we want to schedule this fiber as having work
    
  348.       // on its children. We'll use the childLanes on
    
  349.       // this fiber to indicate that a context has changed.
    
  350.       scheduleContextWorkOnParentPath(
    
  351.         parentSuspense,
    
  352.         renderLanes,
    
  353.         workInProgress,
    
  354.       );
    
  355.       nextFiber = fiber.sibling;
    
  356.     } else {
    
  357.       // Traverse down.
    
  358.       nextFiber = fiber.child;
    
  359.     }
    
  360. 
    
  361.     if (nextFiber !== null) {
    
  362.       // Set the return pointer of the child to the work-in-progress fiber.
    
  363.       nextFiber.return = fiber;
    
  364.     } else {
    
  365.       // No child. Traverse to next sibling.
    
  366.       nextFiber = fiber;
    
  367.       while (nextFiber !== null) {
    
  368.         if (nextFiber === workInProgress) {
    
  369.           // We're back to the root of this subtree. Exit.
    
  370.           nextFiber = null;
    
  371.           break;
    
  372.         }
    
  373.         const sibling = nextFiber.sibling;
    
  374.         if (sibling !== null) {
    
  375.           // Set the return pointer of the sibling to the work-in-progress fiber.
    
  376.           sibling.return = nextFiber.return;
    
  377.           nextFiber = sibling;
    
  378.           break;
    
  379.         }
    
  380.         // No more siblings. Traverse up.
    
  381.         nextFiber = nextFiber.return;
    
  382.       }
    
  383.     }
    
  384.     fiber = nextFiber;
    
  385.   }
    
  386. }
    
  387. 
    
  388. function propagateContextChanges<T>(
    
  389.   workInProgress: Fiber,
    
  390.   contexts: Array<any>,
    
  391.   renderLanes: Lanes,
    
  392.   forcePropagateEntireTree: boolean,
    
  393. ): void {
    
  394.   // Only used by lazy implementation
    
  395.   if (!enableLazyContextPropagation) {
    
  396.     return;
    
  397.   }
    
  398.   let fiber = workInProgress.child;
    
  399.   if (fiber !== null) {
    
  400.     // Set the return pointer of the child to the work-in-progress fiber.
    
  401.     fiber.return = workInProgress;
    
  402.   }
    
  403.   while (fiber !== null) {
    
  404.     let nextFiber;
    
  405. 
    
  406.     // Visit this fiber.
    
  407.     const list = fiber.dependencies;
    
  408.     if (list !== null) {
    
  409.       nextFiber = fiber.child;
    
  410. 
    
  411.       let dep = list.firstContext;
    
  412.       findChangedDep: while (dep !== null) {
    
  413.         // Assigning these to constants to help Flow
    
  414.         const dependency = dep;
    
  415.         const consumer = fiber;
    
  416.         findContext: for (let i = 0; i < contexts.length; i++) {
    
  417.           const context: ReactContext<T> = contexts[i];
    
  418.           // Check if the context matches.
    
  419.           // TODO: Compare selected values to bail out early.
    
  420.           if (dependency.context === context) {
    
  421.             // Match! Schedule an update on this fiber.
    
  422. 
    
  423.             // In the lazy implementation, don't mark a dirty flag on the
    
  424.             // dependency itself. Not all changes are propagated, so we can't
    
  425.             // rely on the propagation function alone to determine whether
    
  426.             // something has changed; the consumer will check. In the future, we
    
  427.             // could add back a dirty flag as an optimization to avoid double
    
  428.             // checking, but until we have selectors it's not really worth
    
  429.             // the trouble.
    
  430.             consumer.lanes = mergeLanes(consumer.lanes, renderLanes);
    
  431.             const alternate = consumer.alternate;
    
  432.             if (alternate !== null) {
    
  433.               alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
    
  434.             }
    
  435.             scheduleContextWorkOnParentPath(
    
  436.               consumer.return,
    
  437.               renderLanes,
    
  438.               workInProgress,
    
  439.             );
    
  440. 
    
  441.             if (!forcePropagateEntireTree) {
    
  442.               // During lazy propagation, when we find a match, we can defer
    
  443.               // propagating changes to the children, because we're going to
    
  444.               // visit them during render. We should continue propagating the
    
  445.               // siblings, though
    
  446.               nextFiber = null;
    
  447.             }
    
  448. 
    
  449.             // Since we already found a match, we can stop traversing the
    
  450.             // dependency list.
    
  451.             break findChangedDep;
    
  452.           }
    
  453.         }
    
  454.         dep = dependency.next;
    
  455.       }
    
  456.     } else if (fiber.tag === DehydratedFragment) {
    
  457.       // If a dehydrated suspense boundary is in this subtree, we don't know
    
  458.       // if it will have any context consumers in it. The best we can do is
    
  459.       // mark it as having updates.
    
  460.       const parentSuspense = fiber.return;
    
  461. 
    
  462.       if (parentSuspense === null) {
    
  463.         throw new Error(
    
  464.           'We just came from a parent so we must have had a parent. This is a bug in React.',
    
  465.         );
    
  466.       }
    
  467. 
    
  468.       parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
    
  469.       const alternate = parentSuspense.alternate;
    
  470.       if (alternate !== null) {
    
  471.         alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
    
  472.       }
    
  473.       // This is intentionally passing this fiber as the parent
    
  474.       // because we want to schedule this fiber as having work
    
  475.       // on its children. We'll use the childLanes on
    
  476.       // this fiber to indicate that a context has changed.
    
  477.       scheduleContextWorkOnParentPath(
    
  478.         parentSuspense,
    
  479.         renderLanes,
    
  480.         workInProgress,
    
  481.       );
    
  482.       nextFiber = null;
    
  483.     } else {
    
  484.       // Traverse down.
    
  485.       nextFiber = fiber.child;
    
  486.     }
    
  487. 
    
  488.     if (nextFiber !== null) {
    
  489.       // Set the return pointer of the child to the work-in-progress fiber.
    
  490.       nextFiber.return = fiber;
    
  491.     } else {
    
  492.       // No child. Traverse to next sibling.
    
  493.       nextFiber = fiber;
    
  494.       while (nextFiber !== null) {
    
  495.         if (nextFiber === workInProgress) {
    
  496.           // We're back to the root of this subtree. Exit.
    
  497.           nextFiber = null;
    
  498.           break;
    
  499.         }
    
  500.         const sibling = nextFiber.sibling;
    
  501.         if (sibling !== null) {
    
  502.           // Set the return pointer of the sibling to the work-in-progress fiber.
    
  503.           sibling.return = nextFiber.return;
    
  504.           nextFiber = sibling;
    
  505.           break;
    
  506.         }
    
  507.         // No more siblings. Traverse up.
    
  508.         nextFiber = nextFiber.return;
    
  509.       }
    
  510.     }
    
  511.     fiber = nextFiber;
    
  512.   }
    
  513. }
    
  514. 
    
  515. export function lazilyPropagateParentContextChanges(
    
  516.   current: Fiber,
    
  517.   workInProgress: Fiber,
    
  518.   renderLanes: Lanes,
    
  519. ) {
    
  520.   const forcePropagateEntireTree = false;
    
  521.   propagateParentContextChanges(
    
  522.     current,
    
  523.     workInProgress,
    
  524.     renderLanes,
    
  525.     forcePropagateEntireTree,
    
  526.   );
    
  527. }
    
  528. 
    
  529. // Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
    
  530. // to the entire subtree, because we won't revisit it until after the current
    
  531. // render has completed, at which point we'll have lost track of which providers
    
  532. // have changed.
    
  533. export function propagateParentContextChangesToDeferredTree(
    
  534.   current: Fiber,
    
  535.   workInProgress: Fiber,
    
  536.   renderLanes: Lanes,
    
  537. ) {
    
  538.   const forcePropagateEntireTree = true;
    
  539.   propagateParentContextChanges(
    
  540.     current,
    
  541.     workInProgress,
    
  542.     renderLanes,
    
  543.     forcePropagateEntireTree,
    
  544.   );
    
  545. }
    
  546. 
    
  547. function propagateParentContextChanges(
    
  548.   current: Fiber,
    
  549.   workInProgress: Fiber,
    
  550.   renderLanes: Lanes,
    
  551.   forcePropagateEntireTree: boolean,
    
  552. ) {
    
  553.   if (!enableLazyContextPropagation) {
    
  554.     return;
    
  555.   }
    
  556. 
    
  557.   // Collect all the parent providers that changed. Since this is usually small
    
  558.   // number, we use an Array instead of Set.
    
  559.   let contexts = null;
    
  560.   let parent: null | Fiber = workInProgress;
    
  561.   let isInsidePropagationBailout = false;
    
  562.   while (parent !== null) {
    
  563.     if (!isInsidePropagationBailout) {
    
  564.       if ((parent.flags & NeedsPropagation) !== NoFlags) {
    
  565.         isInsidePropagationBailout = true;
    
  566.       } else if ((parent.flags & DidPropagateContext) !== NoFlags) {
    
  567.         break;
    
  568.       }
    
  569.     }
    
  570. 
    
  571.     if (parent.tag === ContextProvider) {
    
  572.       const currentParent = parent.alternate;
    
  573. 
    
  574.       if (currentParent === null) {
    
  575.         throw new Error('Should have a current fiber. This is a bug in React.');
    
  576.       }
    
  577. 
    
  578.       const oldProps = currentParent.memoizedProps;
    
  579.       if (oldProps !== null) {
    
  580.         const providerType: ReactProviderType<any> = parent.type;
    
  581.         const context: ReactContext<any> = providerType._context;
    
  582. 
    
  583.         const newProps = parent.pendingProps;
    
  584.         const newValue = newProps.value;
    
  585. 
    
  586.         const oldValue = oldProps.value;
    
  587. 
    
  588.         if (!is(newValue, oldValue)) {
    
  589.           if (contexts !== null) {
    
  590.             contexts.push(context);
    
  591.           } else {
    
  592.             contexts = [context];
    
  593.           }
    
  594.         }
    
  595.       }
    
  596.     } else if (
    
  597.       enableFormActions &&
    
  598.       enableAsyncActions &&
    
  599.       parent === getHostTransitionProvider()
    
  600.     ) {
    
  601.       // During a host transition, a host component can act like a context
    
  602.       // provider. E.g. in React DOM, this would be a <form />.
    
  603.       const currentParent = parent.alternate;
    
  604.       if (currentParent === null) {
    
  605.         throw new Error('Should have a current fiber. This is a bug in React.');
    
  606.       }
    
  607. 
    
  608.       const oldStateHook: Hook = currentParent.memoizedState;
    
  609.       const oldState: TransitionStatus = oldStateHook.memoizedState;
    
  610. 
    
  611.       const newStateHook: Hook = parent.memoizedState;
    
  612.       const newState: TransitionStatus = newStateHook.memoizedState;
    
  613. 
    
  614.       // This uses regular equality instead of Object.is because we assume that
    
  615.       // host transition state doesn't include NaN as a valid type.
    
  616.       if (oldState !== newState) {
    
  617.         if (contexts !== null) {
    
  618.           contexts.push(HostTransitionContext);
    
  619.         } else {
    
  620.           contexts = [HostTransitionContext];
    
  621.         }
    
  622.       }
    
  623.     }
    
  624.     parent = parent.return;
    
  625.   }
    
  626. 
    
  627.   if (contexts !== null) {
    
  628.     // If there were any changed providers, search through the children and
    
  629.     // propagate their changes.
    
  630.     propagateContextChanges(
    
  631.       workInProgress,
    
  632.       contexts,
    
  633.       renderLanes,
    
  634.       forcePropagateEntireTree,
    
  635.     );
    
  636.   }
    
  637. 
    
  638.   // This is an optimization so that we only propagate once per subtree. If a
    
  639.   // deeply nested child bails out, and it calls this propagation function, it
    
  640.   // uses this flag to know that the remaining ancestor providers have already
    
  641.   // been propagated.
    
  642.   //
    
  643.   // NOTE: This optimization is only necessary because we sometimes enter the
    
  644.   // begin phase of nodes that don't have any work scheduled on them —
    
  645.   // specifically, the siblings of a node that _does_ have scheduled work. The
    
  646.   // siblings will bail out and call this function again, even though we already
    
  647.   // propagated content changes to it and its subtree. So we use this flag to
    
  648.   // mark that the parent providers already propagated.
    
  649.   //
    
  650.   // Unfortunately, though, we need to ignore this flag when we're inside a
    
  651.   // tree whose context propagation was deferred — that's what the
    
  652.   // `NeedsPropagation` flag is for.
    
  653.   //
    
  654.   // If we could instead bail out before entering the siblings' begin phase,
    
  655.   // then we could remove both `DidPropagateContext` and `NeedsPropagation`.
    
  656.   // Consider this as part of the next refactor to the fiber tree structure.
    
  657.   workInProgress.flags |= DidPropagateContext;
    
  658. }
    
  659. 
    
  660. export function checkIfContextChanged(
    
  661.   currentDependencies: Dependencies,
    
  662. ): boolean {
    
  663.   if (!enableLazyContextPropagation) {
    
  664.     return false;
    
  665.   }
    
  666.   // Iterate over the current dependencies to see if something changed. This
    
  667.   // only gets called if props and state has already bailed out, so it's a
    
  668.   // relatively uncommon path, except at the root of a changed subtree.
    
  669.   // Alternatively, we could move these comparisons into `readContext`, but
    
  670.   // that's a much hotter path, so I think this is an appropriate trade off.
    
  671.   let dependency = currentDependencies.firstContext;
    
  672.   while (dependency !== null) {
    
  673.     const context = dependency.context;
    
  674.     const newValue = isPrimaryRenderer
    
  675.       ? context._currentValue
    
  676.       : context._currentValue2;
    
  677.     const oldValue = dependency.memoizedValue;
    
  678.     if (!is(newValue, oldValue)) {
    
  679.       return true;
    
  680.     }
    
  681.     dependency = dependency.next;
    
  682.   }
    
  683.   return false;
    
  684. }
    
  685. 
    
  686. export function prepareToReadContext(
    
  687.   workInProgress: Fiber,
    
  688.   renderLanes: Lanes,
    
  689. ): void {
    
  690.   currentlyRenderingFiber = workInProgress;
    
  691.   lastContextDependency = null;
    
  692.   lastFullyObservedContext = null;
    
  693. 
    
  694.   const dependencies = workInProgress.dependencies;
    
  695.   if (dependencies !== null) {
    
  696.     if (enableLazyContextPropagation) {
    
  697.       // Reset the work-in-progress list
    
  698.       dependencies.firstContext = null;
    
  699.     } else {
    
  700.       const firstContext = dependencies.firstContext;
    
  701.       if (firstContext !== null) {
    
  702.         if (includesSomeLane(dependencies.lanes, renderLanes)) {
    
  703.           // Context list has a pending update. Mark that this fiber performed work.
    
  704.           markWorkInProgressReceivedUpdate();
    
  705.         }
    
  706.         // Reset the work-in-progress list
    
  707.         dependencies.firstContext = null;
    
  708.       }
    
  709.     }
    
  710.   }
    
  711. }
    
  712. 
    
  713. export function readContext<T>(context: ReactContext<T>): T {
    
  714.   if (__DEV__) {
    
  715.     // This warning would fire if you read context inside a Hook like useMemo.
    
  716.     // Unlike the class check below, it's not enforced in production for perf.
    
  717.     if (isDisallowedContextReadInDEV) {
    
  718.       console.error(
    
  719.         'Context can only be read while React is rendering. ' +
    
  720.           'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
    
  721.           'In function components, you can read it directly in the function body, but not ' +
    
  722.           'inside Hooks like useReducer() or useMemo().',
    
  723.       );
    
  724.     }
    
  725.   }
    
  726.   return readContextForConsumer(currentlyRenderingFiber, context);
    
  727. }
    
  728. 
    
  729. export function readContextDuringReconcilation<T>(
    
  730.   consumer: Fiber,
    
  731.   context: ReactContext<T>,
    
  732.   renderLanes: Lanes,
    
  733. ): T {
    
  734.   if (currentlyRenderingFiber === null) {
    
  735.     prepareToReadContext(consumer, renderLanes);
    
  736.   }
    
  737.   return readContextForConsumer(consumer, context);
    
  738. }
    
  739. 
    
  740. function readContextForConsumer<T>(
    
  741.   consumer: Fiber | null,
    
  742.   context: ReactContext<T>,
    
  743. ): T {
    
  744.   const value = isPrimaryRenderer
    
  745.     ? context._currentValue
    
  746.     : context._currentValue2;
    
  747. 
    
  748.   if (lastFullyObservedContext === context) {
    
  749.     // Nothing to do. We already observe everything in this context.
    
  750.   } else {
    
  751.     const contextItem = {
    
  752.       context: ((context: any): ReactContext<mixed>),
    
  753.       memoizedValue: value,
    
  754.       next: null,
    
  755.     };
    
  756. 
    
  757.     if (lastContextDependency === null) {
    
  758.       if (consumer === null) {
    
  759.         throw new Error(
    
  760.           'Context can only be read while React is rendering. ' +
    
  761.             'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
    
  762.             'In function components, you can read it directly in the function body, but not ' +
    
  763.             'inside Hooks like useReducer() or useMemo().',
    
  764.         );
    
  765.       }
    
  766. 
    
  767.       // This is the first dependency for this component. Create a new list.
    
  768.       lastContextDependency = contextItem;
    
  769.       consumer.dependencies = {
    
  770.         lanes: NoLanes,
    
  771.         firstContext: contextItem,
    
  772.       };
    
  773.       if (enableLazyContextPropagation) {
    
  774.         consumer.flags |= NeedsPropagation;
    
  775.       }
    
  776.     } else {
    
  777.       // Append a new context item.
    
  778.       lastContextDependency = lastContextDependency.next = contextItem;
    
  779.     }
    
  780.   }
    
  781.   return value;
    
  782. }