/** * 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 {ReactNodeList, ReactCustomFormAction} from 'shared/ReactTypes'; import type { CrossOriginEnum, PreloadImplOptions, PreloadModuleImplOptions, PreinitStyleOptions, PreinitScriptOptions, PreinitModuleScriptOptions, ImportMap, } from 'react-dom/src/shared/ReactDOMTypes'; import { checkHtmlStringCoercion, checkCSSPropertyStringCoercion, checkAttributeStringCoercion, } from 'shared/CheckStringCoercion'; import {Children} from 'react'; import { enableFilterEmptyStringAttributesDOM, enableCustomElementPropertySupport, enableFloat, enableFormActions, enableFizzExternalRuntime, } from 'shared/ReactFeatureFlags'; import type { Destination, Chunk, PrecomputedChunk, } from 'react-server/src/ReactServerStreamConfig'; import type {FormStatus} from '../shared/ReactDOMFormActions'; import { writeChunk, writeChunkAndReturn, stringToChunk, stringToPrecomputedChunk, clonePrecomputedChunk, } from 'react-server/src/ReactServerStreamConfig'; import { resolveRequest, getResumableState, getRenderState, flushResources, } from 'react-server/src/ReactFizzServer'; import isAttributeNameSafe from '../shared/isAttributeNameSafe'; import isUnitlessNumber from '../shared/isUnitlessNumber'; import getAttributeAlias from '../shared/getAttributeAlias'; import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes'; import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook'; import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook'; import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook'; import warnValidStyle from '../shared/warnValidStyle'; import escapeTextForBrowser from './escapeTextForBrowser'; import hyphenateStyleName from '../shared/hyphenateStyleName'; import hasOwnProperty from 'shared/hasOwnProperty'; import sanitizeURL from '../shared/sanitizeURL'; import isArray from 'shared/isArray'; import { clientRenderBoundary as clientRenderFunction, completeBoundary as completeBoundaryFunction, completeBoundaryWithStyles as styleInsertionFunction, completeSegment as completeSegmentFunction, formReplaying as formReplayingRuntime, } from './fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings'; import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMResourceValidation'; import {NotPending} from '../shared/ReactDOMFormActions'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; const ReactDOMServerDispatcher = { prefetchDNS, preconnect, preload, preloadModule, preinitStyle, preinitScript, preinitModuleScript, }; export function prepareHostDispatcher() { ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher; } // Used to distinguish these contexts from ones used in other renderers. // E.g. this can be used to distinguish legacy renderers from this modern one. export const isPrimaryRenderer = true; export type StreamingFormat = 0 | 1; const ScriptStreamingFormat: StreamingFormat = 0; const DataStreamingFormat: StreamingFormat = 1; export type InstructionState = number; const NothingSent /* */ = 0b00000; const SentCompleteSegmentFunction /* */ = 0b00001; const SentCompleteBoundaryFunction /* */ = 0b00010; const SentClientRenderFunction /* */ = 0b00100; const SentStyleInsertionFunction /* */ = 0b01000; const SentFormReplayingRuntime /* */ = 0b10000; // Per request, global state that is not contextual to the rendering subtree. // This cannot be resumed and therefore should only contain things that are // temporary working state or are never used in the prerender pass. export type RenderState = { // These can be recreated from resumable state. placeholderPrefix: PrecomputedChunk, segmentPrefix: PrecomputedChunk, boundaryPrefix: PrecomputedChunk, // inline script streaming format, unused if using external runtime / data startInlineScript: PrecomputedChunk, // the preamble must always flush before resuming, so all these chunks must // be null or empty when resuming. // preamble chunks htmlChunks: null | Array, headChunks: null | Array, // external runtime script chunks externalRuntimeScript: null | ExternalRuntimeScript, bootstrapChunks: Array, // Hoistable chunks charsetChunks: Array, preconnectChunks: Array, importMapChunks: Array, preloadChunks: Array, hoistableChunks: Array, // Flushing queues for Resource dependencies preconnects: Set, fontPreloads: Set, highImagePreloads: Set, // usedImagePreloads: Set, styles: Map, bootstrapScripts: Set, scripts: Set, bulkPreloads: Set, // Temporarily keeps track of key to preload resources before shell flushes. preloads: { images: Map, stylesheets: Map, scripts: Map, moduleScripts: Map, }, // Module-global-like reference for current boundary resources boundaryResources: ?BoundaryResources, // Module-global-like reference for flushing/hoisting state of style resources // We need to track whether the current request has flushed any style resources // without sending an instruction to hoist them. we do that here stylesToHoist: boolean, // We allow the legacy renderer to extend this object. ... }; type Exists = null; type Preloaded = []; // Credentials here are things that affect whether a browser will make a request // as well as things that affect which connection the browser will use for that request. // We want these to be aligned across preloads and resources because otherwise the preload // will be wasted. // We investigated whether referrerPolicy should be included here but from experimentation // it seems that browsers do not treat this as part of the http cache key and does not affect // which connection is used. type PreloadedWithCredentials = [ /* crossOrigin */ ?string, /* integrity */ ?string, ]; const EXISTS: Exists = null; // This constant is to mark preloads that have no unique credentials // to convey. It should never be checked by identity and we should not // assume Preload values in ResumableState equal this value because they // will have come from some parsed input. const PRELOAD_NO_CREDS: Preloaded = []; if (__DEV__) { Object.freeze(PRELOAD_NO_CREDS); } // Per response, global state that is not contextual to the rendering subtree. // This is resumable and therefore should be serializable. export type ResumableState = { idPrefix: string, nextFormID: number, streamingFormat: StreamingFormat, // state for script streaming format, unused if using external runtime / data instructions: InstructionState, // postamble state hasBody: boolean, hasHtml: boolean, // Resources - Request local cache unknownResources: { [asType: string]: { [href: string]: Preloaded, }, }, dnsResources: {[key: string]: Exists}, connectResources: { default: {[key: string]: Exists}, anonymous: {[key: string]: Exists}, credentials: {[key: string]: Exists}, }, imageResources: { [key: string]: Preloaded, }, styleResources: { [key: string]: Exists | Preloaded | PreloadedWithCredentials, }, scriptResources: { [key: string]: Exists | Preloaded | PreloadedWithCredentials, }, moduleUnknownResources: { [asType: string]: { [href: string]: Preloaded, }, }, moduleScriptResources: { [key: string]: Exists | Preloaded | PreloadedWithCredentials, }, }; const dataElementQuotedEnd = stringToPrecomputedChunk('">'); const startInlineScript = stringToPrecomputedChunk(''); const startScriptSrc = stringToPrecomputedChunk(''); /** * This escaping function is designed to work with bootstrapScriptContent and importMap only. * because we know we are escaping the entire script. We can avoid for instance * escaping html comment string sequences that are valid javascript as well because * if there are no sebsequent '); // Allows us to keep track of what we've already written so we can refer back to it. // if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag // is set, the server will send instructions via data attributes (instead of inline scripts) export function createRenderState( resumableState: ResumableState, nonce: string | void, bootstrapScriptContent: string | void, bootstrapScripts: $ReadOnlyArray | void, bootstrapModules: $ReadOnlyArray | void, externalRuntimeConfig: string | BootstrapScriptDescriptor | void, importMap: ImportMap | void, ): RenderState { const inlineScriptWithNonce = nonce === undefined ? startInlineScript : stringToPrecomputedChunk( ''); const completeSegmentData1 = stringToPrecomputedChunk( '