/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import type {DOMEventName} from '../events/DOMEventNames'; import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type { BoundingRect, IntersectionObserverOptions, ObserveVisibleRectsCallback, } from 'react-reconciler/src/ReactTestSelectors'; import type {ReactContext, ReactScopeInstance} from 'shared/ReactTypes'; import type {AncestorInfoDev} from './validateDOMNesting'; import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; import type { CrossOriginEnum, PreloadImplOptions, PreloadModuleImplOptions, PreinitStyleOptions, PreinitScriptOptions, PreinitModuleScriptOptions, } from 'react-dom/src/shared/ReactDOMTypes'; import type {TransitionTypes} from 'react/src/ReactTransitionType'; import {NotPending} from '../shared/ReactDOMFormActions'; import {setSrcObject} from './ReactDOMSrcObject'; import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber'; import hasOwnProperty from 'shared/hasOwnProperty'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; export { setCurrentUpdatePriority, getCurrentUpdatePriority, resolveUpdatePriority, } from './ReactDOMUpdatePriority'; import { precacheFiberNode, updateFiberProps, getFiberCurrentPropsFromNode, getInstanceFromNode, getClosestInstanceFromNode, getFiberFromScopeInstance, getInstanceFromNode as getInstanceFromNodeDOMTree, isContainerMarkedAsRoot, detachDeletedInstance, getResourcesFromRoot, isMarkedHoistable, markNodeAsHoistable, markNodeAsPendingLoad, clearPendingLoadOnNode, isNodePendingLoad, isOwnedInstance, } from './ReactDOMComponentTree'; import { traverseFragmentInstancesAndTextInstances, getFragmentParentInstanceOrContainerFiber, getInstanceFromHostFiber, isFiberFollowing, isFiberPreceding, getFragmentInstanceOrTextInstanceSiblings, traverseFragmentInstancesAndTextInstancesDeeply, fiberIsPortaledIntoHost, getFragmentPortalContainerInfo, isFiberContainedByFragment, isFragmentContainedByFiber, } from 'react-reconciler/src/ReactFiberTreeReflection'; import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared'; export {detachDeletedInstance}; import {hasRole} from './DOMAccessibilityRoles'; import type {SingletonType} from './ReactDOMComponent'; import { setInitialProperties, updateProperties, clearSingletonProperties, hydrateProperties, hydrateText, diffHydratedProperties, getPropsFromElement, diffHydratedText, trapClickOnNonInteractiveElement, clearClickListener, } from './ReactDOMComponent'; import {hydrateInput} from './ReactDOMInput'; import {hydrateTextarea} from './ReactDOMTextarea'; import {hydrateSelect} from './ReactDOMSelect'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; import { validateDOMNesting, validateTextNesting, updatedAncestorInfoDev, } from './validateDOMNesting'; import { isEnabled as ReactBrowserEventEmitterIsEnabled, setEnabled as ReactBrowserEventEmitterSetEnabled, } from '../events/ReactDOMEventListener'; import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces'; import { ELEMENT_NODE, TEXT_NODE, COMMENT_NODE, DOCUMENT_NODE, DOCUMENT_TYPE_NODE, DOCUMENT_FRAGMENT_NODE, } from './HTMLNodeType'; import { flushEventReplaying, retryIfBlockedOn, } from '../events/ReactDOMEventReplaying'; import { enableCreateEventHandleAPI, enableScopeAPI, enableTrustedTypesIntegration, disableLegacyMode, enableMoveBefore, disableCommentsAsDOMContainers, enableSuspenseyImages, enableSrcObject, enableViewTransition, enableHydrationChangeEvent, enableFragmentRefsScrollIntoView, enableProfilerTimer, enableFragmentRefsInstanceHandles, enableFragmentRefsTextNodes, } from 'shared/ReactFeatureFlags'; import { HostComponent, HostHoistable, HostText, HostSingleton, } from 'react-reconciler/src/ReactWorkTags'; import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem'; import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation'; import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes'; import {flushSyncWork as flushSyncWorkOnAllRoots} from 'react-reconciler/src/ReactFiberWorkLoop'; import {requestFormReset as requestFormResetOnFiber} from 'react-reconciler/src/ReactFiberHooks'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; export {default as rendererVersion} from 'shared/ReactVersion'; import noop from 'shared/noop'; import estimateBandwidth from './estimateBandwidth'; export const rendererPackageName = 'react-dom'; export const extraDevToolsConfig = null; export type Type = string; export type Props = { autoFocus?: boolean, children?: mixed, disabled?: boolean, hidden?: boolean, suppressHydrationWarning?: boolean, dangerouslySetInnerHTML?: mixed, style?: { display?: string, viewTransitionName?: string, 'view-transition-name'?: string, viewTransitionClass?: string, 'view-transition-class'?: string, margin?: string, marginTop?: string, 'margin-top'?: string, marginBottom?: string, 'margin-bottom'?: string, ... }, bottom?: null | number, left?: null | number, right?: null | number, top?: null | number, is?: string, size?: number, value?: string, defaultValue?: string, checked?: boolean, defaultChecked?: boolean, multiple?: boolean, type?: string, src?: string | Blob | MediaSource | MediaStream, // TODO: Response srcSet?: string, loading?: 'eager' | 'lazy', onLoad?: (event: any) => void, ... }; type RawProps = { [string]: mixed, }; export type EventTargetChildElement = { type: string, props: null | { style?: { position?: string, zIndex?: number, bottom?: string, left?: string, right?: string, top?: string, ... }, ... }, ... }; export type Container = | interface extends Element {_reactRootContainer?: FiberRoot} | interface extends Document {_reactRootContainer?: FiberRoot} | interface extends DocumentFragment {_reactRootContainer?: FiberRoot}; export type Instance = Element; export type TextInstance = Text; type InstanceWithFragmentHandles = Instance & { reactFragments?: Set, }; type HostNodeWithFragmentHandles = (Instance | TextInstance) & { reactFragments?: Set, }; declare class ActivityInterface extends Comment {} declare class SuspenseInterface extends Comment { _reactRetry: void | (() => void); } export type ActivityInstance = ActivityInterface; export type SuspenseInstance = SuspenseInterface; type FormStateMarkerInstance = Comment; export type HydratableInstance = | Instance | TextInstance | ActivityInstance | SuspenseInstance | FormStateMarkerInstance; export type PublicInstance = Element | Text; export type HostContextDev = { context: HostContextProd, ancestorInfo: AncestorInfoDev, }; type HostContextProd = HostContextNamespace; export type HostContext = HostContextDev | HostContextProd; export type UpdatePayload = Array; export type ChildSet = void; // Unused export type TimeoutHandle = TimeoutID; export type NoTimeout = -1; export type RendererInspectionConfig = $ReadOnly<{}>; export type TransitionStatus = FormStatus; export type ViewTransitionInstance = { name: string, group: mixin$Animatable, imagePair: mixin$Animatable, old: mixin$Animatable, new: mixin$Animatable, }; type SelectionInformation = { focusedElem: null | HTMLElement, selectionRange: mixed, }; const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning'; const ACTIVITY_START_DATA = '&'; const ACTIVITY_END_DATA = '/&'; const SUSPENSE_START_DATA = '$'; const SUSPENSE_END_DATA = '/$'; const SUSPENSE_PENDING_START_DATA = '$?'; const SUSPENSE_QUEUED_START_DATA = '$~'; const SUSPENSE_FALLBACK_START_DATA = '$!'; const PREAMBLE_CONTRIBUTION_HTML = 'html'; const PREAMBLE_CONTRIBUTION_BODY = 'body'; const PREAMBLE_CONTRIBUTION_HEAD = 'head'; const FORM_STATE_IS_MATCHING = 'F!'; const FORM_STATE_IS_NOT_MATCHING = 'F'; const DOCUMENT_READY_STATE_LOADING = 'loading'; const STYLE = 'style'; opaque type HostContextNamespace = 0 | 1 | 2; export const HostContextNamespaceNone: HostContextNamespace = 0; const HostContextNamespaceSvg: HostContextNamespace = 1; const HostContextNamespaceMath: HostContextNamespace = 2; let eventsEnabled: ?boolean = null; let selectionInformation: null | SelectionInformation = null; export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence'; function getOwnerDocumentFromRootContainer( rootContainerElement: Element | Document | DocumentFragment, ): Document { return rootContainerElement.nodeType === DOCUMENT_NODE ? (rootContainerElement as any) : rootContainerElement.ownerDocument; } export function getRootHostContext( rootContainerInstance: Container, ): HostContext { let type; let context: HostContextProd; const nodeType = rootContainerInstance.nodeType; switch (nodeType) { case DOCUMENT_NODE: case DOCUMENT_FRAGMENT_NODE: { type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment'; const root = (rootContainerInstance as any).documentElement; if (root) { const namespaceURI = root.namespaceURI; context = namespaceURI ? getOwnHostContext(namespaceURI) : HostContextNamespaceNone; } else { context = HostContextNamespaceNone; } break; } default: { const container: any = !disableCommentsAsDOMContainers && nodeType === COMMENT_NODE ? rootContainerInstance.parentNode : rootContainerInstance; type = container.tagName; const namespaceURI = container.namespaceURI; if (!namespaceURI) { switch (type) { case 'svg': context = HostContextNamespaceSvg; break; case 'math': context = HostContextNamespaceMath; break; default: context = HostContextNamespaceNone; break; } } else { const ownContext = getOwnHostContext(namespaceURI); context = getChildHostContextProd(ownContext, type); } break; } } if (__DEV__) { const validatedTag = type.toLowerCase(); const ancestorInfo = updatedAncestorInfoDev(null, validatedTag); return {context, ancestorInfo}; } return context; } function getOwnHostContext(namespaceURI: string): HostContextNamespace { switch (namespaceURI) { case SVG_NAMESPACE: return HostContextNamespaceSvg; case MATH_NAMESPACE: return HostContextNamespaceMath; default: return HostContextNamespaceNone; } } function getChildHostContextProd( parentNamespace: HostContextNamespace, type: string, ): HostContextNamespace { if (parentNamespace === HostContextNamespaceNone) { // No (or default) parent namespace: potential entry point. switch (type) { case 'svg': return HostContextNamespaceSvg; case 'math': return HostContextNamespaceMath; default: return HostContextNamespaceNone; } } if (parentNamespace === HostContextNamespaceSvg && type === 'foreignObject') { // We're leaving SVG. return HostContextNamespaceNone; } // By default, pass namespace below. return parentNamespace; } export function getChildHostContext( parentHostContext: HostContext, type: string, ): HostContext { if (__DEV__) { const parentHostContextDev = parentHostContext as any as HostContextDev; const context = getChildHostContextProd(parentHostContextDev.context, type); const ancestorInfo = updatedAncestorInfoDev( parentHostContextDev.ancestorInfo, type, ); return {context, ancestorInfo}; } const parentNamespace = parentHostContext as any as HostContextProd; return getChildHostContextProd(parentNamespace, type); } export function getPublicInstance(instance: Instance): Instance { return instance; } export function prepareForCommit(containerInfo: Container): Object | null { eventsEnabled = ReactBrowserEventEmitterIsEnabled(); selectionInformation = getSelectionInformation(containerInfo); let activeInstance = null; if (enableCreateEventHandleAPI) { const focusedElem = selectionInformation.focusedElem; if (focusedElem !== null) { activeInstance = getClosestInstanceFromNode(focusedElem); } } ReactBrowserEventEmitterSetEnabled(false); return activeInstance; } export function beforeActiveInstanceBlur(internalInstanceHandle: Object): void { if (enableCreateEventHandleAPI) { ReactBrowserEventEmitterSetEnabled(true); dispatchBeforeDetachedBlur( (selectionInformation as any).focusedElem, internalInstanceHandle, ); ReactBrowserEventEmitterSetEnabled(false); } } export function afterActiveInstanceBlur(): void { if (enableCreateEventHandleAPI) { ReactBrowserEventEmitterSetEnabled(true); dispatchAfterDetachedBlur((selectionInformation as any).focusedElem); ReactBrowserEventEmitterSetEnabled(false); } } export function resetAfterCommit(containerInfo: Container): void { restoreSelection(selectionInformation, containerInfo); ReactBrowserEventEmitterSetEnabled(eventsEnabled); eventsEnabled = null; selectionInformation = null; } export function createHoistableInstance( type: string, props: Props, rootContainerInstance: Container, internalInstanceHandle: Object, ): Instance { const ownerDocument = getOwnerDocumentFromRootContainer( rootContainerInstance, ); const domElement: Instance = ownerDocument.createElement(type); precacheFiberNode(internalInstanceHandle, domElement); updateFiberProps(domElement, props); setInitialProperties(domElement, type, props); markNodeAsHoistable(domElement); return domElement; } let didWarnScriptTags = false; function isScriptDataBlock(props: Props): boolean { const scriptType = props.type; if (typeof scriptType !== 'string' || scriptType === '') { return false; } const lower = scriptType.toLowerCase(); // Special non-MIME keywords recognized by the HTML spec // TODO: May be fine to also not warn about having these types be parsed as "parser-inserted" if ( lower === 'module' || lower === 'importmap' || lower === 'speculationrules' ) { return false; } // JavaScript MIME types per https://mimesniff.spec.whatwg.org/#javascript-mime-type switch (lower) { case 'application/ecmascript': case 'application/javascript': case 'application/x-ecmascript': case 'application/x-javascript': case 'text/ecmascript': case 'text/javascript': case 'text/javascript1.0': case 'text/javascript1.1': case 'text/javascript1.2': case 'text/javascript1.3': case 'text/javascript1.4': case 'text/javascript1.5': case 'text/jscript': case 'text/livescript': case 'text/x-ecmascript': case 'text/x-javascript': return false; } // Any other non-empty type value means this is a data block return true; } const warnedUnknownTags: { [key: string]: boolean, } = { // There are working polyfills for . Let people use it. dialog: true, // Electron ships a custom tag to display external web content in // an isolated frame and process. // This tag is not present in non Electron environments such as JSDom which // is often used for testing purposes. // @see https://electronjs.org/docs/api/webview-tag webview: true, }; export function createInstance( type: string, props: Props, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: Object, ): Instance { let hostContextProd: HostContextProd; if (__DEV__) { // TODO: take namespace into account when validating. const hostContextDev: HostContextDev = hostContext as any; validateDOMNesting(type, hostContextDev.ancestorInfo); hostContextProd = hostContextDev.context; } else { hostContextProd = hostContext as any; } const ownerDocument = getOwnerDocumentFromRootContainer( rootContainerInstance, ); let domElement: Instance; switch (hostContextProd) { case HostContextNamespaceSvg: domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type); break; case HostContextNamespaceMath: domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type); break; default: switch (type) { case 'svg': { domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type); break; } case 'math': { domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type); break; } case 'script': { // Create the script via .innerHTML so its "parser-inserted" flag is // set to true and it does not execute const div = ownerDocument.createElement('div'); if (__DEV__) { if ( enableTrustedTypesIntegration && !didWarnScriptTags && // Data block scripts are not executed by UAs anyway so // we don't need to warn: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type !isScriptDataBlock(props) ) { console.error( 'Encountered a script tag while rendering React component. ' + 'Scripts inside React components are never executed when rendering ' + 'on the client. Consider using template tag instead ' + '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).', ); didWarnScriptTags = true; } } div.innerHTML = '