main
js 243 lines 6.44 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 isAttributeNameSafe from '../shared/isAttributeNameSafe';
11 import {enableTrustedTypesIntegration} from 'shared/ReactFeatureFlags';
12 import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
13 import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree';
14 import {trackHostMutation} from 'react-reconciler/src/ReactFiberMutationTracking';
15
16 /**
17 * Get the value for a attribute on a node. Only used in DEV for SSR validation.
18 * The third argument is used as a hint of what the expected value is. Some
19 * attributes have multiple equivalent values.
20 */
21 export function getValueForAttribute(
22 node: Element,
23 name: string,
24 expected: mixed,
25 ): mixed {
26 if (__DEV__) {
27 if (!isAttributeNameSafe(name)) {
28 return;
29 }
30 if (!node.hasAttribute(name)) {
31 // shouldRemoveAttribute
32 switch (typeof expected) {
33 case 'function':
34 case 'symbol':
35 return expected;
36 case 'boolean': {
37 const prefix = name.toLowerCase().slice(0, 5);
38 if (prefix !== 'data-' && prefix !== 'aria-') {
39 return expected;
40 }
41 }
42 }
43 return expected === undefined ? undefined : null;
44 }
45 // When CSP is enabled, browsers hide the nonce attribute
46 // so we need to access the nonce property directly
47 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cryptographicnonce
48 const isNonce = name.toLowerCase() === 'nonce';
49 const value = isNonce ? (node as any).nonce : node.getAttribute(name);
50 if (__DEV__) {
51 checkAttributeStringCoercion(expected, name);
52 }
53 if (value === '' + (expected as any)) {
54 return expected;
55 }
56 return value;
57 }
58 }
59
60 export function getValueForAttributeOnCustomComponent(
61 node: Element,
62 name: string,
63 expected: mixed,
64 ): mixed {
65 if (__DEV__) {
66 if (!isAttributeNameSafe(name)) {
67 return;
68 }
69 if (!node.hasAttribute(name)) {
70 // shouldRemoveAttribute
71 switch (typeof expected) {
72 case 'symbol':
73 case 'object':
74 // Symbols and objects are ignored when they're emitted so
75 // it would be expected that they end up not having an attribute.
76 return expected;
77 case 'function':
78 return expected;
79 case 'boolean':
80 if (expected === false) {
81 return expected;
82 }
83 }
84 return expected === undefined ? undefined : null;
85 }
86 // When CSP is enabled, browsers hide the nonce attribute
87 // so we need to access the nonce property directly
88 // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cryptographicnonce
89 const isNonce = name.toLowerCase() === 'nonce';
90 const value = isNonce ? (node as any).nonce : node.getAttribute(name);
91
92 if (value === '' && expected === true) {
93 return true;
94 }
95
96 if (__DEV__) {
97 checkAttributeStringCoercion(expected, name);
98 }
99 if (value === '' + (expected as any)) {
100 return expected;
101 }
102 return value;
103 }
104 }
105
106 export function setValueForAttribute(
107 node: Element,
108 name: string,
109 value: mixed,
110 ) {
111 if (isAttributeNameSafe(name)) {
112 // If the prop isn't in the special list, treat it as a simple attribute.
113 // shouldRemoveAttribute
114 if (value === null) {
115 node.removeAttribute(name);
116 return;
117 }
118 switch (typeof value) {
119 case 'undefined':
120 case 'function':
121 case 'symbol':
122 node.removeAttribute(name);
123 return;
124 case 'boolean': {
125 const prefix = name.toLowerCase().slice(0, 5);
126 if (prefix !== 'data-' && prefix !== 'aria-') {
127 node.removeAttribute(name);
128 return;
129 }
130 }
131 }
132 if (__DEV__) {
133 checkAttributeStringCoercion(value, name);
134 }
135 node.setAttribute(
136 name,
137 enableTrustedTypesIntegration ? (value as any) : '' + (value as any),
138 );
139 }
140 }
141
142 export function setValueForKnownAttribute(
143 node: Element,
144 name: string,
145 value: mixed,
146 ) {
147 if (value === null) {
148 node.removeAttribute(name);
149 return;
150 }
151 switch (typeof value) {
152 case 'undefined':
153 case 'function':
154 case 'symbol':
155 case 'boolean': {
156 node.removeAttribute(name);
157 return;
158 }
159 }
160 if (__DEV__) {
161 checkAttributeStringCoercion(value, name);
162 }
163 node.setAttribute(
164 name,
165 enableTrustedTypesIntegration ? (value as any) : '' + (value as any),
166 );
167 }
168
169 export function setValueForNamespacedAttribute(
170 node: Element,
171 namespace: string,
172 name: string,
173 value: mixed,
174 ) {
175 if (value === null) {
176 node.removeAttribute(name);
177 return;
178 }
179 switch (typeof value) {
180 case 'undefined':
181 case 'function':
182 case 'symbol':
183 case 'boolean': {
184 node.removeAttribute(name);
185 return;
186 }
187 }
188 if (__DEV__) {
189 checkAttributeStringCoercion(value, name);
190 }
191 node.setAttributeNS(
192 namespace,
193 name,
194 enableTrustedTypesIntegration ? (value as any) : '' + (value as any),
195 );
196 }
197
198 export function setValueForPropertyOnCustomComponent(
199 node: Element,
200 name: string,
201 value: mixed,
202 ) {
203 if (name[0] === 'o' && name[1] === 'n') {
204 const useCapture = name.endsWith('Capture');
205 const eventName = name.slice(2, useCapture ? name.length - 7 : undefined);
206
207 const prevProps = getFiberCurrentPropsFromNode(node);
208 // $FlowFixMe[invalid-computed-prop]
209 const prevValue = prevProps != null ? prevProps[name] : null;
210 if (typeof prevValue === 'function') {
211 node.removeEventListener(eventName, prevValue, useCapture);
212 }
213 if (typeof value === 'function') {
214 if (typeof prevValue !== 'function' && prevValue !== null) {
215 // If we previously assigned a non-function type into this node, then
216 // remove it when switching to event listener mode.
217 if (name in (node as any)) {
218 (node as any)[name] = null;
219 } else if (node.hasAttribute(name)) {
220 node.removeAttribute(name);
221 }
222 }
223 // $FlowFixMe[incompatible-type] value can't be casted to EventListener.
224 node.addEventListener(eventName, value as EventListener, useCapture);
225 return;
226 }
227 }
228
229 trackHostMutation();
230
231 if (name in (node as any)) {
232 (node as any)[name] = value;
233 return;
234 }
235
236 if (value === true) {
237 node.setAttribute(name, '');
238 return;
239 }
240
241 // From here, it's the same as any attribute
242 setValueForAttribute(node, name, value);
243 }