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} from 'shared/ReactTypes';
    
  11. 
    
  12. import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
    
  13. import {isPrimaryRenderer} from './ReactFizzConfig';
    
  14. 
    
  15. let rendererSigil;
    
  16. if (__DEV__) {
    
  17.   // Use this to detect multiple renderers using the same context
    
  18.   rendererSigil = {};
    
  19. }
    
  20. 
    
  21. // Used to store the parent path of all context overrides in a shared linked list.
    
  22. // Forming a reverse tree.
    
  23. type ContextNode<T> = {
    
  24.   parent: null | ContextNode<any>,
    
  25.   depth: number, // Short hand to compute the depth of the tree at this node.
    
  26.   context: ReactContext<T>,
    
  27.   parentValue: T,
    
  28.   value: T,
    
  29. };
    
  30. 
    
  31. // The structure of a context snapshot is an implementation of this file.
    
  32. // Currently, it's implemented as tracking the current active node.
    
  33. export opaque type ContextSnapshot = null | ContextNode<any>;
    
  34. 
    
  35. export const rootContextSnapshot: ContextSnapshot = null;
    
  36. 
    
  37. // We assume that this runtime owns the "current" field on all ReactContext instances.
    
  38. // This global (actually thread local) state represents what state all those "current",
    
  39. // fields are currently in.
    
  40. let currentActiveSnapshot: ContextSnapshot = null;
    
  41. 
    
  42. function popNode(prev: ContextNode<any>): void {
    
  43.   if (isPrimaryRenderer) {
    
  44.     prev.context._currentValue = prev.parentValue;
    
  45.   } else {
    
  46.     prev.context._currentValue2 = prev.parentValue;
    
  47.   }
    
  48. }
    
  49. 
    
  50. function pushNode(next: ContextNode<any>): void {
    
  51.   if (isPrimaryRenderer) {
    
  52.     next.context._currentValue = next.value;
    
  53.   } else {
    
  54.     next.context._currentValue2 = next.value;
    
  55.   }
    
  56. }
    
  57. 
    
  58. function popToNearestCommonAncestor(
    
  59.   prev: ContextNode<any>,
    
  60.   next: ContextNode<any>,
    
  61. ): void {
    
  62.   if (prev === next) {
    
  63.     // We've found a shared ancestor. We don't need to pop nor reapply this one or anything above.
    
  64.   } else {
    
  65.     popNode(prev);
    
  66.     const parentPrev = prev.parent;
    
  67.     const parentNext = next.parent;
    
  68.     if (parentPrev === null) {
    
  69.       if (parentNext !== null) {
    
  70.         throw new Error(
    
  71.           'The stacks must reach the root at the same time. This is a bug in React.',
    
  72.         );
    
  73.       }
    
  74.     } else {
    
  75.       if (parentNext === null) {
    
  76.         throw new Error(
    
  77.           'The stacks must reach the root at the same time. This is a bug in React.',
    
  78.         );
    
  79.       }
    
  80. 
    
  81.       popToNearestCommonAncestor(parentPrev, parentNext);
    
  82.     }
    
  83. 
    
  84.     // On the way back, we push the new ones that weren't common.
    
  85.     pushNode(next);
    
  86.   }
    
  87. }
    
  88. 
    
  89. function popAllPrevious(prev: ContextNode<any>): void {
    
  90.   popNode(prev);
    
  91.   const parentPrev = prev.parent;
    
  92.   if (parentPrev !== null) {
    
  93.     popAllPrevious(parentPrev);
    
  94.   }
    
  95. }
    
  96. 
    
  97. function pushAllNext(next: ContextNode<any>): void {
    
  98.   const parentNext = next.parent;
    
  99.   if (parentNext !== null) {
    
  100.     pushAllNext(parentNext);
    
  101.   }
    
  102.   pushNode(next);
    
  103. }
    
  104. 
    
  105. function popPreviousToCommonLevel(
    
  106.   prev: ContextNode<any>,
    
  107.   next: ContextNode<any>,
    
  108. ): void {
    
  109.   popNode(prev);
    
  110.   const parentPrev = prev.parent;
    
  111. 
    
  112.   if (parentPrev === null) {
    
  113.     throw new Error(
    
  114.       'The depth must equal at least at zero before reaching the root. This is a bug in React.',
    
  115.     );
    
  116.   }
    
  117. 
    
  118.   if (parentPrev.depth === next.depth) {
    
  119.     // We found the same level. Now we just need to find a shared ancestor.
    
  120.     popToNearestCommonAncestor(parentPrev, next);
    
  121.   } else {
    
  122.     // We must still be deeper.
    
  123.     popPreviousToCommonLevel(parentPrev, next);
    
  124.   }
    
  125. }
    
  126. 
    
  127. function popNextToCommonLevel(
    
  128.   prev: ContextNode<any>,
    
  129.   next: ContextNode<any>,
    
  130. ): void {
    
  131.   const parentNext = next.parent;
    
  132. 
    
  133.   if (parentNext === null) {
    
  134.     throw new Error(
    
  135.       'The depth must equal at least at zero before reaching the root. This is a bug in React.',
    
  136.     );
    
  137.   }
    
  138. 
    
  139.   if (prev.depth === parentNext.depth) {
    
  140.     // We found the same level. Now we just need to find a shared ancestor.
    
  141.     popToNearestCommonAncestor(prev, parentNext);
    
  142.   } else {
    
  143.     // We must still be deeper.
    
  144.     popNextToCommonLevel(prev, parentNext);
    
  145.   }
    
  146.   pushNode(next);
    
  147. }
    
  148. 
    
  149. // Perform context switching to the new snapshot.
    
  150. // To make it cheap to read many contexts, while not suspending, we make the switch eagerly by
    
  151. // updating all the context's current values. That way reads, always just read the current value.
    
  152. // At the cost of updating contexts even if they're never read by this subtree.
    
  153. export function switchContext(newSnapshot: ContextSnapshot): void {
    
  154.   // The basic algorithm we need to do is to pop back any contexts that are no longer on the stack.
    
  155.   // We also need to update any new contexts that are now on the stack with the deepest value.
    
  156.   // The easiest way to update new contexts is to just reapply them in reverse order from the
    
  157.   // perspective of the backpointers. To avoid allocating a lot when switching, we use the stack
    
  158.   // for that. Therefore this algorithm is recursive.
    
  159.   // 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go.
    
  160.   // 2) Then we find the nearest common ancestor from there. Popping old contexts as we go.
    
  161.   // 3) Then we reapply new contexts on the way back up the stack.
    
  162.   const prev = currentActiveSnapshot;
    
  163.   const next = newSnapshot;
    
  164.   if (prev !== next) {
    
  165.     if (prev === null) {
    
  166.       // $FlowFixMe[incompatible-call]: This has to be non-null since it's not equal to prev.
    
  167.       pushAllNext(next);
    
  168.     } else if (next === null) {
    
  169.       popAllPrevious(prev);
    
  170.     } else if (prev.depth === next.depth) {
    
  171.       popToNearestCommonAncestor(prev, next);
    
  172.     } else if (prev.depth > next.depth) {
    
  173.       popPreviousToCommonLevel(prev, next);
    
  174.     } else {
    
  175.       popNextToCommonLevel(prev, next);
    
  176.     }
    
  177.     currentActiveSnapshot = next;
    
  178.   }
    
  179. }
    
  180. 
    
  181. export function pushProvider<T>(
    
  182.   context: ReactContext<T>,
    
  183.   nextValue: T,
    
  184. ): ContextSnapshot {
    
  185.   let prevValue;
    
  186.   if (isPrimaryRenderer) {
    
  187.     prevValue = context._currentValue;
    
  188.     context._currentValue = nextValue;
    
  189.     if (__DEV__) {
    
  190.       if (
    
  191.         context._currentRenderer !== undefined &&
    
  192.         context._currentRenderer !== null &&
    
  193.         context._currentRenderer !== rendererSigil
    
  194.       ) {
    
  195.         console.error(
    
  196.           'Detected multiple renderers concurrently rendering the ' +
    
  197.             'same context provider. This is currently unsupported.',
    
  198.         );
    
  199.       }
    
  200.       context._currentRenderer = rendererSigil;
    
  201.     }
    
  202.   } else {
    
  203.     prevValue = context._currentValue2;
    
  204.     context._currentValue2 = nextValue;
    
  205.     if (__DEV__) {
    
  206.       if (
    
  207.         context._currentRenderer2 !== undefined &&
    
  208.         context._currentRenderer2 !== null &&
    
  209.         context._currentRenderer2 !== rendererSigil
    
  210.       ) {
    
  211.         console.error(
    
  212.           'Detected multiple renderers concurrently rendering the ' +
    
  213.             'same context provider. This is currently unsupported.',
    
  214.         );
    
  215.       }
    
  216.       context._currentRenderer2 = rendererSigil;
    
  217.     }
    
  218.   }
    
  219.   const prevNode = currentActiveSnapshot;
    
  220.   const newNode: ContextNode<T> = {
    
  221.     parent: prevNode,
    
  222.     depth: prevNode === null ? 0 : prevNode.depth + 1,
    
  223.     context: context,
    
  224.     parentValue: prevValue,
    
  225.     value: nextValue,
    
  226.   };
    
  227.   currentActiveSnapshot = newNode;
    
  228.   return newNode;
    
  229. }
    
  230. 
    
  231. export function popProvider<T>(context: ReactContext<T>): ContextSnapshot {
    
  232.   const prevSnapshot = currentActiveSnapshot;
    
  233. 
    
  234.   if (prevSnapshot === null) {
    
  235.     throw new Error(
    
  236.       'Tried to pop a Context at the root of the app. This is a bug in React.',
    
  237.     );
    
  238.   }
    
  239. 
    
  240.   if (__DEV__) {
    
  241.     if (prevSnapshot.context !== context) {
    
  242.       console.error(
    
  243.         'The parent context is not the expected context. This is probably a bug in React.',
    
  244.       );
    
  245.     }
    
  246.   }
    
  247.   if (isPrimaryRenderer) {
    
  248.     const value = prevSnapshot.parentValue;
    
  249.     if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
    
  250.       prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
    
  251.     } else {
    
  252.       prevSnapshot.context._currentValue = value;
    
  253.     }
    
  254.     if (__DEV__) {
    
  255.       if (
    
  256.         context._currentRenderer !== undefined &&
    
  257.         context._currentRenderer !== null &&
    
  258.         context._currentRenderer !== rendererSigil
    
  259.       ) {
    
  260.         console.error(
    
  261.           'Detected multiple renderers concurrently rendering the ' +
    
  262.             'same context provider. This is currently unsupported.',
    
  263.         );
    
  264.       }
    
  265.       context._currentRenderer = rendererSigil;
    
  266.     }
    
  267.   } else {
    
  268.     const value = prevSnapshot.parentValue;
    
  269.     if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
    
  270.       prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
    
  271.     } else {
    
  272.       prevSnapshot.context._currentValue2 = value;
    
  273.     }
    
  274.     if (__DEV__) {
    
  275.       if (
    
  276.         context._currentRenderer2 !== undefined &&
    
  277.         context._currentRenderer2 !== null &&
    
  278.         context._currentRenderer2 !== rendererSigil
    
  279.       ) {
    
  280.         console.error(
    
  281.           'Detected multiple renderers concurrently rendering the ' +
    
  282.             'same context provider. This is currently unsupported.',
    
  283.         );
    
  284.       }
    
  285.       context._currentRenderer2 = rendererSigil;
    
  286.     }
    
  287.   }
    
  288.   return (currentActiveSnapshot = prevSnapshot.parent);
    
  289. }
    
  290. 
    
  291. export function getActiveContext(): ContextSnapshot {
    
  292.   return currentActiveSnapshot;
    
  293. }
    
  294. 
    
  295. export function readContext<T>(context: ReactContext<T>): T {
    
  296.   const value = isPrimaryRenderer
    
  297.     ? context._currentValue
    
  298.     : context._currentValue2;
    
  299.   return value;
    
  300. }