@samitouri / QOS-React-2 / commits / 8f42196892

Change DOM HostContext to number instead of string (#26698)

In React DOM, we use HostContext to represent the namespace of whatever is currently rendering — SVG, Math, or HTML. Because there is a fixed set of possible values, we can switch this to be a number instead. My motivation is that I want to start tracking additional information in this type, and I want to pack all of it into a single number instead of turning it into an object. For better performance. (In dev, the host context type is already an object that includes additional information, but that's dev so who cares.) Technically, before this change, the host context could be any namespace URI string, but any value other than SVG or Math was treated the same way. Only SVG and Math have special behavior. So in the new structure, there are three enum values: SVG, Math, or None, which represents the HTML namespace as well as all other possible namespaces.

Andrew Clark committed Apr 21, 2023 at 21:44 UTC 8f42196892847a3dd1ab4c84eda0c8d52508ecf6
3 files changed +121 -89
packages/react-dom-bindings/src/client/DOMNamespaces.js
-29
@@ -7,34 +7,5 @@
7 * @flow
8 */
9
10 -export const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
10 export const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
11 export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
13 -
14 -// Assumes there is no parent namespace.
15 -export function getIntrinsicNamespace(type: string): string {
16 - switch (type) {
17 - case 'svg':
18 - return SVG_NAMESPACE;
19 - case 'math':
20 - return MATH_NAMESPACE;
21 - default:
22 - return HTML_NAMESPACE;
23 - }
24 -}
25 -
26 -export function getChildNamespace(
27 - parentNamespace: string | null,
28 - type: string,
29 -): string {
30 - if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
31 - // No (or default) parent namespace: potential entry point.
32 - return getIntrinsicNamespace(type);
33 - }
34 - if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
35 - // We're leaving SVG.
36 - return HTML_NAMESPACE;
37 - }
38 - // By default, pass namespace below.
39 - return parentNamespace;
40 -}
packages/react-dom-bindings/src/client/ReactDOMComponent.js
+33 -20
@@ -7,6 +7,10 @@
7 * @flow
8 */
9
10 +import type {HostContext, HostContextDev} from './ReactFiberConfigDOM';
11 +
12 +import {HostContextNamespaceNone} from './ReactFiberConfigDOM';
13 +
14 import {
15 registrationNameDependencies,
16 possibleRegistrationNames,
@@ -53,7 +57,7 @@ import {
57 setValueForStyles,
58 validateShorthandPropertyCollisionInDev,
59 } from './CSSPropertyOperations';
56 -import {HTML_NAMESPACE, getIntrinsicNamespace} from './DOMNamespaces';
60 +import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';
61 import isCustomElement from '../shared/isCustomElement';
62 import getAttributeAlias from '../shared/getAttributeAlias';
63 import possibleStandardNames from '../shared/possibleStandardNames';
@@ -290,12 +294,13 @@ function normalizeHTML(parent: Element, html: string) {
294 // how <noscript> is being handled. So we use the same document.
295 // See the discussion in https://github.com/facebook/react/pull/11157.
296 const testElement =
293 - parent.namespaceURI === HTML_NAMESPACE
294 - ? parent.ownerDocument.createElement(parent.tagName)
295 - : parent.ownerDocument.createElementNS(
297 + parent.namespaceURI === MATH_NAMESPACE ||
298 + parent.namespaceURI === SVG_NAMESPACE
299 + ? parent.ownerDocument.createElementNS(
300 (parent.namespaceURI: any),
301 parent.tagName,
298 - );
302 + )
303 + : parent.ownerDocument.createElement(parent.tagName);
304 testElement.innerHTML = html;
305 return testElement.innerHTML;
306 }
@@ -2469,7 +2474,7 @@ function diffHydratedCustomComponent(
2474 domElement: Element,
2475 tag: string,
2476 props: Object,
2472 - parentNamespaceDev: string,
2477 + hostContext: HostContext,
2478 extraAttributes: Set<string>,
2479 ) {
2480 for (const propKey in props) {
@@ -2545,11 +2550,14 @@ function diffHydratedCustomComponent(
2550 }
2551 // Fall through
2552 default: {
2548 - let ownNamespaceDev = parentNamespaceDev;
2549 - if (ownNamespaceDev === HTML_NAMESPACE) {
2550 - ownNamespaceDev = getIntrinsicNamespace(tag);
2551 - }
2552 - if (ownNamespaceDev === HTML_NAMESPACE) {
2553 + // This is a DEV-only path
2554 + const hostContextDev: HostContextDev = (hostContext: any);
2555 + const hostContextProd = hostContextDev.context;
2556 + if (
2557 + hostContextProd === HostContextNamespaceNone &&
2558 + tag !== 'svg' &&
2559 + tag !== 'math'
2560 + ) {
2561 extraAttributes.delete(propKey.toLowerCase());
2562 } else {
2563 extraAttributes.delete(propKey);
@@ -2576,7 +2584,7 @@ function diffHydratedGenericElement(
2584 domElement: Element,
2585 tag: string,
2586 props: Object,
2579 - parentNamespaceDev: string,
2587 + hostContext: HostContext,
2588 extraAttributes: Set<string>,
2589 ) {
2590 for (const propKey in props) {
@@ -2945,11 +2953,16 @@ function diffHydratedGenericElement(
2953 }
2954 const attributeName = getAttributeAlias(propKey);
2955 let isMismatchDueToBadCasing = false;
2948 - let ownNamespaceDev = parentNamespaceDev;
2949 - if (ownNamespaceDev === HTML_NAMESPACE) {
2950 - ownNamespaceDev = getIntrinsicNamespace(tag);
2951 - }
2952 - if (ownNamespaceDev === HTML_NAMESPACE) {
2956 +
2957 + // This is a DEV-only path
2958 + const hostContextDev: HostContextDev = (hostContext: any);
2959 + const hostContextProd = hostContextDev.context;
2960 +
2961 + if (
2962 + hostContextProd === HostContextNamespaceNone &&
2963 + tag !== 'svg' &&
2964 + tag !== 'math'
2965 + ) {
2966 extraAttributes.delete(attributeName.toLowerCase());
2967 } else {
2968 const standardName = getPossibleStandardName(propKey);
@@ -2983,7 +2996,7 @@ export function diffHydratedProperties(
2996 props: Object,
2997 isConcurrentMode: boolean,
2998 shouldWarnDev: boolean,
2986 - parentNamespaceDev: string,
2999 + hostContext: HostContext,
3000 ): null | Array<mixed> {
3001 if (__DEV__) {
3002 validatePropertiesInDevelopment(tag, props);
@@ -3155,7 +3168,7 @@ export function diffHydratedProperties(
3168 domElement,
3169 tag,
3170 props,
3158 - parentNamespaceDev,
3171 + hostContext,
3172 extraAttributes,
3173 );
3174 } else {
@@ -3163,7 +3176,7 @@ export function diffHydratedProperties(
3176 domElement,
3177 tag,
3178 props,
3166 - parentNamespaceDev,
3179 + hostContext,
3180 extraAttributes,
3181 );
3182 }
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+88 -40
@@ -67,11 +67,7 @@ import {
67 setEnabled as ReactBrowserEventEmitterSetEnabled,
68 getEventPriority,
69 } from '../events/ReactDOMEventListener';
70 -import {
71 - getChildNamespace,
72 - SVG_NAMESPACE,
73 - MATH_NAMESPACE,
74 -} from './DOMNamespaces';
70 +import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';
71 import {
72 ELEMENT_NODE,
73 TEXT_NODE,
@@ -154,11 +150,11 @@ export interface SuspenseInstance extends Comment {
150 }
151 export type HydratableInstance = Instance | TextInstance | SuspenseInstance;
152 export type PublicInstance = Element | Text;
157 -type HostContextDev = {
158 - namespace: HostContextProd,
153 +export type HostContextDev = {
154 + context: HostContextProd,
155 ancestorInfo: AncestorInfoDev,
156 };
161 -type HostContextProd = string;
157 +type HostContextProd = HostContextNamespace;
158 export type HostContext = HostContextDev | HostContextProd;
159 export type UpdatePayload = Array<mixed>;
160 export type ChildSet = void; // Unused
@@ -180,6 +176,11 @@ const SUSPENSE_FALLBACK_START_DATA = '$!';
176
177 const STYLE = 'style';
178
179 +opaque type HostContextNamespace = 0 | 1 | 2;
180 +export const HostContextNamespaceNone: HostContextNamespace = 0;
181 +const HostContextNamespaceSvg: HostContextNamespace = 1;
182 +const HostContextNamespaceMath: HostContextNamespace = 2;
183 +
184 let eventsEnabled: ?boolean = null;
185 let selectionInformation: null | SelectionInformation = null;
186
@@ -197,14 +198,21 @@ export function getRootHostContext(
198 rootContainerInstance: Container,
199 ): HostContext {
200 let type;
200 - let namespace: HostContextProd;
201 + let context: HostContextProd;
202 const nodeType = rootContainerInstance.nodeType;
203 switch (nodeType) {
204 case DOCUMENT_NODE:
205 case DOCUMENT_FRAGMENT_NODE: {
206 type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
207 const root = (rootContainerInstance: any).documentElement;
207 - namespace = root ? root.namespaceURI : getChildNamespace(null, '');
208 + if (root) {
209 + const namespaceURI = root.namespaceURI;
210 + context = namespaceURI
211 + ? getOwnHostContext(namespaceURI)
212 + : HostContextNamespaceNone;
213 + } else {
214 + context = HostContextNamespaceNone;
215 + }
216 break;
217 }
218 default: {
@@ -212,18 +220,67 @@ export function getRootHostContext(
220 nodeType === COMMENT_NODE
221 ? rootContainerInstance.parentNode
222 : rootContainerInstance;
215 - const ownNamespace = container.namespaceURI || null;
223 type = container.tagName;
217 - namespace = getChildNamespace(ownNamespace, type);
224 + const namespaceURI = container.namespaceURI;
225 + if (!namespaceURI) {
226 + switch (type) {
227 + case 'svg':
228 + context = HostContextNamespaceSvg;
229 + break;
230 + case 'math':
231 + context = HostContextNamespaceMath;
232 + break;
233 + default:
234 + context = HostContextNamespaceNone;
235 + break;
236 + }
237 + } else {
238 + const ownContext = getOwnHostContext(namespaceURI);
239 + context = getChildHostContextProd(ownContext, type);
240 + }
241 break;
242 }
243 }
244 if (__DEV__) {
245 const validatedTag = type.toLowerCase();
246 const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);
224 - return {namespace, ancestorInfo};
247 + return {context, ancestorInfo};
248 + }
249 + return context;
250 +}
251 +
252 +function getOwnHostContext(namespaceURI: string): HostContextNamespace {
253 + switch (namespaceURI) {
254 + case SVG_NAMESPACE:
255 + return HostContextNamespaceSvg;
256 + case MATH_NAMESPACE:
257 + return HostContextNamespaceMath;
258 + default:
259 + return HostContextNamespaceNone;
260 + }
261 +}
262 +
263 +function getChildHostContextProd(
264 + parentNamespace: HostContextNamespace,
265 + type: string,
266 +): HostContextNamespace {
267 + if (parentNamespace === HostContextNamespaceNone) {
268 + // No (or default) parent namespace: potential entry point.
269 + switch (type) {
270 + case 'svg':
271 + return HostContextNamespaceSvg;
272 + case 'math':
273 + return HostContextNamespaceMath;
274 + default:
275 + return HostContextNamespaceNone;
276 + }
277 + }
278 + if (parentNamespace === HostContextNamespaceSvg && type === 'foreignObject') {
279 + // We're leaving SVG.
280 + return HostContextNamespaceNone;
281 }
226 - return namespace;
282 + // By default, pass namespace below.
283 + return parentNamespace;
284 }
285
286 export function getChildHostContext(
@@ -232,15 +289,15 @@ export function getChildHostContext(
289 ): HostContext {
290 if (__DEV__) {
291 const parentHostContextDev = ((parentHostContext: any): HostContextDev);
235 - const namespace = getChildNamespace(parentHostContextDev.namespace, type);
292 + const context = getChildHostContextProd(parentHostContextDev.context, type);
293 const ancestorInfo = updatedAncestorInfoDev(
294 parentHostContextDev.ancestorInfo,
295 type,
296 );
240 - return {namespace, ancestorInfo};
297 + return {context, ancestorInfo};
298 }
299 const parentNamespace = ((parentHostContext: any): HostContextProd);
243 - return getChildNamespace(parentNamespace, type);
300 + return getChildHostContextProd(parentNamespace, type);
301 }
302
303 export function getPublicInstance(instance: Instance): Instance {
@@ -326,15 +383,14 @@ export function createInstance(
383 hostContext: HostContext,
384 internalInstanceHandle: Object,
385 ): Instance {
329 - let namespace;
386 + let hostContextProd: HostContextProd;
387 if (__DEV__) {
388 // TODO: take namespace into account when validating.
389 const hostContextDev: HostContextDev = (hostContext: any);
390 validateDOMNesting(type, hostContextDev.ancestorInfo);
334 - namespace = hostContextDev.namespace;
391 + hostContextProd = hostContextDev.context;
392 } else {
336 - const hostContextProd: HostContextProd = (hostContext: any);
337 - namespace = hostContextProd;
393 + hostContextProd = (hostContext: any);
394 }
395
396 const ownerDocument = getOwnerDocumentFromRootContainer(
@@ -342,10 +398,12 @@ export function createInstance(
398 );
399
400 let domElement: Instance;
345 - switch (namespace) {
346 - case SVG_NAMESPACE:
347 - case MATH_NAMESPACE:
348 - domElement = ownerDocument.createElementNS(namespace, type);
401 + switch (hostContextProd) {
402 + case HostContextNamespaceSvg:
403 + domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);
404 + break;
405 + case HostContextNamespaceMath:
406 + domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);
407 break;
408 default:
409 switch (type) {
@@ -1243,22 +1301,13 @@ export function hydrateInstance(
1301 const isConcurrentMode =
1302 ((internalInstanceHandle: Fiber).mode & ConcurrentMode) !== NoMode;
1303
1246 - let parentNamespace;
1247 - if (__DEV__) {
1248 - const hostContextDev = ((hostContext: any): HostContextDev);
1249 - parentNamespace = hostContextDev.namespace;
1250 - } else {
1251 - const hostContextProd = ((hostContext: any): HostContextProd);
1252 - parentNamespace = hostContextProd;
1253 - }
1254 -
1304 return diffHydratedProperties(
1305 instance,
1306 type,
1307 props,
1308 isConcurrentMode,
1309 shouldWarnDev,
1261 - parentNamespace,
1310 + hostContext,
1311 );
1312 }
1313
@@ -2924,20 +2973,19 @@ export function isHostHoistableType(
2973 hostContext: HostContext,
2974 ): boolean {
2975 let outsideHostContainerContext: boolean;
2927 - let namespace: HostContextProd;
2976 + let hostContextProd: HostContextProd;
2977 if (__DEV__) {
2978 const hostContextDev: HostContextDev = (hostContext: any);
2979 // We can only render resources when we are not within the host container context
2980 outsideHostContainerContext =
2981 !hostContextDev.ancestorInfo.containerTagInScope;
2933 - namespace = hostContextDev.namespace;
2982 + hostContextProd = hostContextDev.context;
2983 } else {
2935 - const hostContextProd: HostContextProd = (hostContext: any);
2936 - namespace = hostContextProd;
2984 + hostContextProd = (hostContext: any);
2985 }
2986
2987 // Global opt out of hoisting for anything in SVG Namespace or anything with an itemProp inside an itemScope
2940 - if (namespace === SVG_NAMESPACE || props.itemProp != null) {
2988 + if (hostContextProd === HostContextNamespaceSvg || props.itemProp != null) {
2989 if (__DEV__) {
2990 if (
2991 outsideHostContainerContext &&