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. import type {
    
  13.   Container,
    
  14.   HostContext,
    
  15.   TransitionStatus,
    
  16. } from './ReactFiberConfig';
    
  17. import type {Hook} from './ReactFiberHooks';
    
  18. import type {ReactContext} from 'shared/ReactTypes';
    
  19. 
    
  20. import {
    
  21.   getChildHostContext,
    
  22.   getRootHostContext,
    
  23.   isPrimaryRenderer,
    
  24. } from './ReactFiberConfig';
    
  25. import {createCursor, push, pop} from './ReactFiberStack';
    
  26. import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
    
  27. import {enableAsyncActions, enableFormActions} from 'shared/ReactFeatureFlags';
    
  28. 
    
  29. const contextStackCursor: StackCursor<HostContext | null> = createCursor(null);
    
  30. const contextFiberStackCursor: StackCursor<Fiber | null> = createCursor(null);
    
  31. const rootInstanceStackCursor: StackCursor<Container | null> =
    
  32.   createCursor(null);
    
  33. 
    
  34. // Represents the nearest host transition provider (in React DOM, a <form />)
    
  35. // NOTE: Since forms cannot be nested, and this feature is only implemented by
    
  36. // React DOM, we don't technically need this to be a stack. It could be a single
    
  37. // module variable instead.
    
  38. const hostTransitionProviderCursor: StackCursor<Fiber | null> =
    
  39.   createCursor(null);
    
  40. 
    
  41. // TODO: This should initialize to NotPendingTransition, a constant
    
  42. // imported from the fiber config. However, because of a cycle in the module
    
  43. // graph, that value isn't defined during this module's initialization. I can't
    
  44. // think of a way to work around this without moving that value out of the
    
  45. // fiber config. For now, the "no provider" case is handled when reading,
    
  46. // inside useHostTransitionStatus.
    
  47. export const HostTransitionContext: ReactContext<TransitionStatus | null> = {
    
  48.   $$typeof: REACT_CONTEXT_TYPE,
    
  49.   _currentValue: null,
    
  50.   _currentValue2: null,
    
  51.   _threadCount: 0,
    
  52.   Provider: (null: any),
    
  53.   Consumer: (null: any),
    
  54.   _defaultValue: (null: any),
    
  55.   _globalName: (null: any),
    
  56. };
    
  57. 
    
  58. function requiredContext<Value>(c: Value | null): Value {
    
  59.   if (__DEV__) {
    
  60.     if (c === null) {
    
  61.       console.error(
    
  62.         'Expected host context to exist. This error is likely caused by a bug ' +
    
  63.           'in React. Please file an issue.',
    
  64.       );
    
  65.     }
    
  66.   }
    
  67.   return (c: any);
    
  68. }
    
  69. 
    
  70. function getCurrentRootHostContainer(): null | Container {
    
  71.   return rootInstanceStackCursor.current;
    
  72. }
    
  73. 
    
  74. function getRootHostContainer(): Container {
    
  75.   const rootInstance = requiredContext(rootInstanceStackCursor.current);
    
  76.   return rootInstance;
    
  77. }
    
  78. 
    
  79. export function getHostTransitionProvider(): Fiber | null {
    
  80.   return hostTransitionProviderCursor.current;
    
  81. }
    
  82. 
    
  83. function pushHostContainer(fiber: Fiber, nextRootInstance: Container): void {
    
  84.   // Push current root instance onto the stack;
    
  85.   // This allows us to reset root when portals are popped.
    
  86.   push(rootInstanceStackCursor, nextRootInstance, fiber);
    
  87.   // Track the context and the Fiber that provided it.
    
  88.   // This enables us to pop only Fibers that provide unique contexts.
    
  89.   push(contextFiberStackCursor, fiber, fiber);
    
  90. 
    
  91.   // Finally, we need to push the host context to the stack.
    
  92.   // However, we can't just call getRootHostContext() and push it because
    
  93.   // we'd have a different number of entries on the stack depending on
    
  94.   // whether getRootHostContext() throws somewhere in renderer code or not.
    
  95.   // So we push an empty value first. This lets us safely unwind on errors.
    
  96.   push(contextStackCursor, null, fiber);
    
  97.   const nextRootContext = getRootHostContext(nextRootInstance);
    
  98.   // Now that we know this function doesn't throw, replace it.
    
  99.   pop(contextStackCursor, fiber);
    
  100.   push(contextStackCursor, nextRootContext, fiber);
    
  101. }
    
  102. 
    
  103. function popHostContainer(fiber: Fiber) {
    
  104.   pop(contextStackCursor, fiber);
    
  105.   pop(contextFiberStackCursor, fiber);
    
  106.   pop(rootInstanceStackCursor, fiber);
    
  107. }
    
  108. 
    
  109. function getHostContext(): HostContext {
    
  110.   const context = requiredContext(contextStackCursor.current);
    
  111.   return context;
    
  112. }
    
  113. 
    
  114. function pushHostContext(fiber: Fiber): void {
    
  115.   if (enableFormActions && enableAsyncActions) {
    
  116.     const stateHook: Hook | null = fiber.memoizedState;
    
  117.     if (stateHook !== null) {
    
  118.       // Only provide context if this fiber has been upgraded by a host
    
  119.       // transition. We use the same optimization for regular host context below.
    
  120.       push(hostTransitionProviderCursor, fiber, fiber);
    
  121.     }
    
  122.   }
    
  123. 
    
  124.   const context: HostContext = requiredContext(contextStackCursor.current);
    
  125.   const nextContext = getChildHostContext(context, fiber.type);
    
  126. 
    
  127.   // Don't push this Fiber's context unless it's unique.
    
  128.   if (context !== nextContext) {
    
  129.     // Track the context and the Fiber that provided it.
    
  130.     // This enables us to pop only Fibers that provide unique contexts.
    
  131.     push(contextFiberStackCursor, fiber, fiber);
    
  132.     push(contextStackCursor, nextContext, fiber);
    
  133.   }
    
  134. }
    
  135. 
    
  136. function popHostContext(fiber: Fiber): void {
    
  137.   if (contextFiberStackCursor.current === fiber) {
    
  138.     // Do not pop unless this Fiber provided the current context.
    
  139.     // pushHostContext() only pushes Fibers that provide unique contexts.
    
  140.     pop(contextStackCursor, fiber);
    
  141.     pop(contextFiberStackCursor, fiber);
    
  142.   }
    
  143. 
    
  144.   if (enableFormActions && enableAsyncActions) {
    
  145.     if (hostTransitionProviderCursor.current === fiber) {
    
  146.       // Do not pop unless this Fiber provided the current context. This is mostly
    
  147.       // a performance optimization, but conveniently it also prevents a potential
    
  148.       // data race where a host provider is upgraded (i.e. memoizedState becomes
    
  149.       // non-null) during a concurrent event. This is a bit of a flaw in the way
    
  150.       // we upgrade host components, but because we're accounting for it here, it
    
  151.       // should be fine.
    
  152.       pop(hostTransitionProviderCursor, fiber);
    
  153. 
    
  154.       // When popping the transition provider, we reset the context value back
    
  155.       // to `null`. We can do this because you're not allowd to nest forms. If
    
  156.       // we allowed for multiple nested host transition providers, then we'd
    
  157.       // need to reset this to the parent provider's status.
    
  158.       if (isPrimaryRenderer) {
    
  159.         HostTransitionContext._currentValue = null;
    
  160.       } else {
    
  161.         HostTransitionContext._currentValue2 = null;
    
  162.       }
    
  163.     }
    
  164.   }
    
  165. }
    
  166. 
    
  167. export {
    
  168.   getHostContext,
    
  169.   getCurrentRootHostContainer,
    
  170.   getRootHostContainer,
    
  171.   popHostContainer,
    
  172.   popHostContext,
    
  173.   pushHostContainer,
    
  174.   pushHostContext,
    
  175. };