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