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 {Fiber} from './ReactInternalTypes';
    
  11. import type {StackCursor} from './ReactFiberStack';
    
  12. 
    
  13. import {isFiberMounted} from './ReactFiberTreeReflection';
    
  14. import {disableLegacyContext} from 'shared/ReactFeatureFlags';
    
  15. import {ClassComponent, HostRoot} from './ReactWorkTags';
    
  16. import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
    
  17. import checkPropTypes from 'shared/checkPropTypes';
    
  18. 
    
  19. import {createCursor, push, pop} from './ReactFiberStack';
    
  20. 
    
  21. let warnedAboutMissingGetChildContext;
    
  22. 
    
  23. if (__DEV__) {
    
  24.   warnedAboutMissingGetChildContext = ({}: {[string]: boolean});
    
  25. }
    
  26. 
    
  27. export const emptyContextObject: {} = {};
    
  28. if (__DEV__) {
    
  29.   Object.freeze(emptyContextObject);
    
  30. }
    
  31. 
    
  32. // A cursor to the current merged context object on the stack.
    
  33. const contextStackCursor: StackCursor<Object> =
    
  34.   createCursor(emptyContextObject);
    
  35. // A cursor to a boolean indicating whether the context has changed.
    
  36. const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
    
  37. // Keep track of the previous context object that was on the stack.
    
  38. // We use this to get access to the parent context after we have already
    
  39. // pushed the next context provider, and now need to merge their contexts.
    
  40. let previousContext: Object = emptyContextObject;
    
  41. 
    
  42. function getUnmaskedContext(
    
  43.   workInProgress: Fiber,
    
  44.   Component: Function,
    
  45.   didPushOwnContextIfProvider: boolean,
    
  46. ): Object {
    
  47.   if (disableLegacyContext) {
    
  48.     return emptyContextObject;
    
  49.   } else {
    
  50.     if (didPushOwnContextIfProvider && isContextProvider(Component)) {
    
  51.       // If the fiber is a context provider itself, when we read its context
    
  52.       // we may have already pushed its own child context on the stack. A context
    
  53.       // provider should not "see" its own child context. Therefore we read the
    
  54.       // previous (parent) context instead for a context provider.
    
  55.       return previousContext;
    
  56.     }
    
  57.     return contextStackCursor.current;
    
  58.   }
    
  59. }
    
  60. 
    
  61. function cacheContext(
    
  62.   workInProgress: Fiber,
    
  63.   unmaskedContext: Object,
    
  64.   maskedContext: Object,
    
  65. ): void {
    
  66.   if (disableLegacyContext) {
    
  67.     return;
    
  68.   } else {
    
  69.     const instance = workInProgress.stateNode;
    
  70.     instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
    
  71.     instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
    
  72.   }
    
  73. }
    
  74. 
    
  75. function getMaskedContext(
    
  76.   workInProgress: Fiber,
    
  77.   unmaskedContext: Object,
    
  78. ): Object {
    
  79.   if (disableLegacyContext) {
    
  80.     return emptyContextObject;
    
  81.   } else {
    
  82.     const type = workInProgress.type;
    
  83.     const contextTypes = type.contextTypes;
    
  84.     if (!contextTypes) {
    
  85.       return emptyContextObject;
    
  86.     }
    
  87. 
    
  88.     // Avoid recreating masked context unless unmasked context has changed.
    
  89.     // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
    
  90.     // This may trigger infinite loops if componentWillReceiveProps calls setState.
    
  91.     const instance = workInProgress.stateNode;
    
  92.     if (
    
  93.       instance &&
    
  94.       instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
    
  95.     ) {
    
  96.       return instance.__reactInternalMemoizedMaskedChildContext;
    
  97.     }
    
  98. 
    
  99.     const context: {[string]: $FlowFixMe} = {};
    
  100.     for (const key in contextTypes) {
    
  101.       context[key] = unmaskedContext[key];
    
  102.     }
    
  103. 
    
  104.     if (__DEV__) {
    
  105.       const name = getComponentNameFromFiber(workInProgress) || 'Unknown';
    
  106.       checkPropTypes(contextTypes, context, 'context', name);
    
  107.     }
    
  108. 
    
  109.     // Cache unmasked context so we can avoid recreating masked context unless necessary.
    
  110.     // Context is created before the class component is instantiated so check for instance.
    
  111.     if (instance) {
    
  112.       cacheContext(workInProgress, unmaskedContext, context);
    
  113.     }
    
  114. 
    
  115.     return context;
    
  116.   }
    
  117. }
    
  118. 
    
  119. function hasContextChanged(): boolean {
    
  120.   if (disableLegacyContext) {
    
  121.     return false;
    
  122.   } else {
    
  123.     return didPerformWorkStackCursor.current;
    
  124.   }
    
  125. }
    
  126. 
    
  127. function isContextProvider(type: Function): boolean {
    
  128.   if (disableLegacyContext) {
    
  129.     return false;
    
  130.   } else {
    
  131.     const childContextTypes = type.childContextTypes;
    
  132.     return childContextTypes !== null && childContextTypes !== undefined;
    
  133.   }
    
  134. }
    
  135. 
    
  136. function popContext(fiber: Fiber): void {
    
  137.   if (disableLegacyContext) {
    
  138.     return;
    
  139.   } else {
    
  140.     pop(didPerformWorkStackCursor, fiber);
    
  141.     pop(contextStackCursor, fiber);
    
  142.   }
    
  143. }
    
  144. 
    
  145. function popTopLevelContextObject(fiber: Fiber): void {
    
  146.   if (disableLegacyContext) {
    
  147.     return;
    
  148.   } else {
    
  149.     pop(didPerformWorkStackCursor, fiber);
    
  150.     pop(contextStackCursor, fiber);
    
  151.   }
    
  152. }
    
  153. 
    
  154. function pushTopLevelContextObject(
    
  155.   fiber: Fiber,
    
  156.   context: Object,
    
  157.   didChange: boolean,
    
  158. ): void {
    
  159.   if (disableLegacyContext) {
    
  160.     return;
    
  161.   } else {
    
  162.     if (contextStackCursor.current !== emptyContextObject) {
    
  163.       throw new Error(
    
  164.         'Unexpected context found on stack. ' +
    
  165.           'This error is likely caused by a bug in React. Please file an issue.',
    
  166.       );
    
  167.     }
    
  168. 
    
  169.     push(contextStackCursor, context, fiber);
    
  170.     push(didPerformWorkStackCursor, didChange, fiber);
    
  171.   }
    
  172. }
    
  173. 
    
  174. function processChildContext(
    
  175.   fiber: Fiber,
    
  176.   type: any,
    
  177.   parentContext: Object,
    
  178. ): Object {
    
  179.   if (disableLegacyContext) {
    
  180.     return parentContext;
    
  181.   } else {
    
  182.     const instance = fiber.stateNode;
    
  183.     const childContextTypes = type.childContextTypes;
    
  184. 
    
  185.     // TODO (bvaughn) Replace this behavior with an invariant() in the future.
    
  186.     // It has only been added in Fiber to match the (unintentional) behavior in Stack.
    
  187.     if (typeof instance.getChildContext !== 'function') {
    
  188.       if (__DEV__) {
    
  189.         const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
    
  190. 
    
  191.         if (!warnedAboutMissingGetChildContext[componentName]) {
    
  192.           warnedAboutMissingGetChildContext[componentName] = true;
    
  193.           console.error(
    
  194.             '%s.childContextTypes is specified but there is no getChildContext() method ' +
    
  195.               'on the instance. You can either define getChildContext() on %s or remove ' +
    
  196.               'childContextTypes from it.',
    
  197.             componentName,
    
  198.             componentName,
    
  199.           );
    
  200.         }
    
  201.       }
    
  202.       return parentContext;
    
  203.     }
    
  204. 
    
  205.     const childContext = instance.getChildContext();
    
  206.     for (const contextKey in childContext) {
    
  207.       if (!(contextKey in childContextTypes)) {
    
  208.         throw new Error(
    
  209.           `${
    
  210.             getComponentNameFromFiber(fiber) || 'Unknown'
    
  211.           }.getChildContext(): key "${contextKey}" is not defined in childContextTypes.`,
    
  212.         );
    
  213.       }
    
  214.     }
    
  215.     if (__DEV__) {
    
  216.       const name = getComponentNameFromFiber(fiber) || 'Unknown';
    
  217.       checkPropTypes(childContextTypes, childContext, 'child context', name);
    
  218.     }
    
  219. 
    
  220.     return {...parentContext, ...childContext};
    
  221.   }
    
  222. }
    
  223. 
    
  224. function pushContextProvider(workInProgress: Fiber): boolean {
    
  225.   if (disableLegacyContext) {
    
  226.     return false;
    
  227.   } else {
    
  228.     const instance = workInProgress.stateNode;
    
  229.     // We push the context as early as possible to ensure stack integrity.
    
  230.     // If the instance does not exist yet, we will push null at first,
    
  231.     // and replace it on the stack later when invalidating the context.
    
  232.     const memoizedMergedChildContext =
    
  233.       (instance && instance.__reactInternalMemoizedMergedChildContext) ||
    
  234.       emptyContextObject;
    
  235. 
    
  236.     // Remember the parent context so we can merge with it later.
    
  237.     // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
    
  238.     previousContext = contextStackCursor.current;
    
  239.     push(contextStackCursor, memoizedMergedChildContext, workInProgress);
    
  240.     push(
    
  241.       didPerformWorkStackCursor,
    
  242.       didPerformWorkStackCursor.current,
    
  243.       workInProgress,
    
  244.     );
    
  245. 
    
  246.     return true;
    
  247.   }
    
  248. }
    
  249. 
    
  250. function invalidateContextProvider(
    
  251.   workInProgress: Fiber,
    
  252.   type: any,
    
  253.   didChange: boolean,
    
  254. ): void {
    
  255.   if (disableLegacyContext) {
    
  256.     return;
    
  257.   } else {
    
  258.     const instance = workInProgress.stateNode;
    
  259. 
    
  260.     if (!instance) {
    
  261.       throw new Error(
    
  262.         'Expected to have an instance by this point. ' +
    
  263.           'This error is likely caused by a bug in React. Please file an issue.',
    
  264.       );
    
  265.     }
    
  266. 
    
  267.     if (didChange) {
    
  268.       // Merge parent and own context.
    
  269.       // Skip this if we're not updating due to sCU.
    
  270.       // This avoids unnecessarily recomputing memoized values.
    
  271.       const mergedContext = processChildContext(
    
  272.         workInProgress,
    
  273.         type,
    
  274.         previousContext,
    
  275.       );
    
  276.       instance.__reactInternalMemoizedMergedChildContext = mergedContext;
    
  277. 
    
  278.       // Replace the old (or empty) context with the new one.
    
  279.       // It is important to unwind the context in the reverse order.
    
  280.       pop(didPerformWorkStackCursor, workInProgress);
    
  281.       pop(contextStackCursor, workInProgress);
    
  282.       // Now push the new context and mark that it has changed.
    
  283.       push(contextStackCursor, mergedContext, workInProgress);
    
  284.       push(didPerformWorkStackCursor, didChange, workInProgress);
    
  285.     } else {
    
  286.       pop(didPerformWorkStackCursor, workInProgress);
    
  287.       push(didPerformWorkStackCursor, didChange, workInProgress);
    
  288.     }
    
  289.   }
    
  290. }
    
  291. 
    
  292. function findCurrentUnmaskedContext(fiber: Fiber): Object {
    
  293.   if (disableLegacyContext) {
    
  294.     return emptyContextObject;
    
  295.   } else {
    
  296.     // Currently this is only used with renderSubtreeIntoContainer; not sure if it
    
  297.     // makes sense elsewhere
    
  298.     if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
    
  299.       throw new Error(
    
  300.         'Expected subtree parent to be a mounted class component. ' +
    
  301.           'This error is likely caused by a bug in React. Please file an issue.',
    
  302.       );
    
  303.     }
    
  304. 
    
  305.     let node: Fiber = fiber;
    
  306.     do {
    
  307.       switch (node.tag) {
    
  308.         case HostRoot:
    
  309.           return node.stateNode.context;
    
  310.         case ClassComponent: {
    
  311.           const Component = node.type;
    
  312.           if (isContextProvider(Component)) {
    
  313.             return node.stateNode.__reactInternalMemoizedMergedChildContext;
    
  314.           }
    
  315.           break;
    
  316.         }
    
  317.       }
    
  318.       // $FlowFixMe[incompatible-type] we bail out when we get a null
    
  319.       node = node.return;
    
  320.     } while (node !== null);
    
  321. 
    
  322.     throw new Error(
    
  323.       'Found unexpected detached subtree parent. ' +
    
  324.         'This error is likely caused by a bug in React. Please file an issue.',
    
  325.     );
    
  326.   }
    
  327. }
    
  328. 
    
  329. export {
    
  330.   getUnmaskedContext,
    
  331.   cacheContext,
    
  332.   getMaskedContext,
    
  333.   hasContextChanged,
    
  334.   popContext,
    
  335.   popTopLevelContextObject,
    
  336.   pushTopLevelContextObject,
    
  337.   processChildContext,
    
  338.   isContextProvider,
    
  339.   pushContextProvider,
    
  340.   invalidateContextProvider,
    
  341.   findCurrentUnmaskedContext,
    
  342. };