packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js JAVASCRIPT 7,240 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 7,240.
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {11  ReactNodeList,12  ReactCustomFormAction,13  Thenable,14} from 'shared/ReactTypes';15import type {16  CrossOriginEnum,17  PreloadImplOptions,18  PreloadModuleImplOptions,19  PreinitStyleOptions,20  PreinitScriptOptions,21  PreinitModuleScriptOptions,22  ImportMap,23} from 'react-dom/src/shared/ReactDOMTypes';2425import {26  checkHtmlStringCoercion,27  checkCSSPropertyStringCoercion,28  checkAttributeStringCoercion,29  checkOptionStringCoercion,30} from 'shared/CheckStringCoercion';3132import {Children} from 'react';3334import {35  enableFizzExternalRuntime,36  enableSrcObject,37  enableFizzBlockingRender,38  enableViewTransition,39  enableViewTransitionParentEnterExit,40} from 'shared/ReactFeatureFlags';4142import type {43  Destination,44  Chunk,45  PrecomputedChunk,46} from 'react-server/src/ReactServerStreamConfig';4748import type {FormStatus} from '../shared/ReactDOMFormActions';4950import {51  writeChunk,52  writeChunkAndReturn,53  stringToChunk,54  stringToPrecomputedChunk,55  readAsDataURL,56} from 'react-server/src/ReactServerStreamConfig';57import {58  resolveRequest,59  getResumableState,60  getRenderState,61  flushResources,62} from 'react-server/src/ReactFizzServer';6364import isAttributeNameSafe from '../shared/isAttributeNameSafe';65import isUnitlessNumber from '../shared/isUnitlessNumber';66import getAttributeAlias from '../shared/getAttributeAlias';6768import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';69import {validateProperties as validateARIAProperties} from '../shared/ReactDOMInvalidARIAHook';70import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';71import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';72import warnValidStyle from '../shared/warnValidStyle';73import {getCrossOriginString} from '../shared/crossOriginStrings';7475import escapeTextForBrowser from './escapeTextForBrowser';76import hyphenateStyleName from '../shared/hyphenateStyleName';77import hasOwnProperty from 'shared/hasOwnProperty';78import sanitizeURL from '../shared/sanitizeURL';79import isArray from 'shared/isArray';8081import {82  clientRenderBoundary as clientRenderFunction,83  completeBoundary as completeBoundaryFunction,84  completeBoundaryUpgradeToViewTransitions as upgradeToViewTransitionsInstruction,85  completeBoundaryWithStyles as styleInsertionFunction,86  completeSegment as completeSegmentFunction,87  formReplaying as formReplayingRuntime,88  markShellTime,89} from './fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings';9091import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMResourceValidation';9293import {NotPending} from '../shared/ReactDOMFormActions';9495import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';9697const previousDispatcher =98  ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */99ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ = {100  f /* flushSyncWork */: previousDispatcher.f /* flushSyncWork */,101  r /* requestFormReset */: previousDispatcher.r /* requestFormReset */,102  D /* prefetchDNS */: prefetchDNS,103  C /* preconnect */: preconnect,104  L /* preload */: preload,105  m /* preloadModule */: preloadModule,106  X /* preinitScript */: preinitScript,107  S /* preinitStyle */: preinitStyle,108  M /* preinitModuleScript */: preinitModuleScript,109};110111// We make every property of the descriptor optional because it is not a contract that112// the headers provided by onHeaders has any particular header types.113export type HeadersDescriptor = {114  Link?: string,115};116117// Used to distinguish these contexts from ones used in other renderers.118// E.g. this can be used to distinguish legacy renderers from this modern one.119export const isPrimaryRenderer = true;120121export const supportsClientAPIs = true;122123export type StreamingFormat = 0 | 1;124const ScriptStreamingFormat: StreamingFormat = 0;125const DataStreamingFormat: StreamingFormat = 1;126127export type InstructionState = number;128const NothingSent /*                      */ = 0b000000000;129const SentCompleteSegmentFunction /*      */ = 0b000000001;130const SentCompleteBoundaryFunction /*     */ = 0b000000010;131const SentClientRenderFunction /*         */ = 0b000000100;132const SentStyleInsertionFunction /*       */ = 0b000001000;133const SentFormReplayingRuntime /*         */ = 0b000010000;134const SentCompletedShellId /*             */ = 0b000100000;135const SentMarkShellTime /*                */ = 0b001000000;136const NeedUpgradeToViewTransitions /*     */ = 0b010000000;137const SentUpgradeToViewTransitions /*     */ = 0b100000000;138139type NonceOption =140  | string141  | {142      script?: string,143      style?: string,144    };145146// Per request, global state that is not contextual to the rendering subtree.147// This cannot be resumed and therefore should only contain things that are148// temporary working state or are never used in the prerender pass.149export type RenderState = {150  // These can be recreated from resumable state.151  placeholderPrefix: PrecomputedChunk,152  segmentPrefix: PrecomputedChunk,153  boundaryPrefix: PrecomputedChunk,154155  // inline script streaming format, unused if using external runtime / data156  startInlineScript: PrecomputedChunk,157158  startInlineStyle: PrecomputedChunk,159160  // the preamble must always flush before resuming, so all these chunks must161  // be null or empty when resuming.162163  // preamble chunks164  preamble: PreambleState,165166  // external runtime script chunks167  externalRuntimeScript: null | ExternalRuntimeScript,168  bootstrapChunks: Array<Chunk | PrecomputedChunk>,169  importMapChunks: Array<Chunk | PrecomputedChunk>,170171  // Hoistable chunks172  charsetChunks: Array<Chunk | PrecomputedChunk>,173  viewportChunks: Array<Chunk | PrecomputedChunk>,174  hoistableChunks: Array<Chunk | PrecomputedChunk>,175176  // Headers queues for Resources that can flush early177  onHeaders: void | ((headers: HeadersDescriptor) => void),178  headers: null | {179    preconnects: string,180    fontPreloads: string,181    highImagePreloads: string,182    remainingCapacity: number,183  },184  resets: {185    // corresponds to ResumableState.unknownResources["font"]186    font: {187      [href: string]: Preloaded,188    },189    // the rest correspond to ResumableState[<...>Resources]190    dns: {[key: string]: Exists},191    connect: {192      default: {[key: string]: Exists},193      anonymous: {[key: string]: Exists},194      credentials: {[key: string]: Exists},195    },196    image: {197      [key: string]: Preloaded,198    },199    style: {200      [key: string]: Exists | Preloaded | PreloadedWithCredentials,201    },202  },203204  // Flushing queues for Resource dependencies205  preconnects: Set<Resource>,206  fontPreloads: Set<Resource>,207  highImagePreloads: Set<Resource>,208  // usedImagePreloads: Set<PreloadResource>,209  styles: Map<string, StyleQueue>,210  bootstrapScripts: Set<Resource>,211  scripts: Set<Resource>,212  bulkPreloads: Set<Resource>,213214  // Temporarily keeps track of key to preload resources before shell flushes.215  preloads: {216    images: Map<string, Resource>,217    stylesheets: Map<string, Resource>,218    scripts: Map<string, Resource>,219    moduleScripts: Map<string, Resource>,220  },221222  nonce: {223    script: string | void,224    style: string | void,225  },226227  // Module-global-like reference for flushing/hoisting state of style resources228  // We need to track whether the current request has flushed any style resources229  // without sending an instruction to hoist them. we do that here230  stylesToHoist: boolean,231232  // We allow the legacy renderer to extend this object.233234  ...235};236237type Exists = null;238type Preloaded = [];239// Credentials here are things that affect whether a browser will make a request240// as well as things that affect which connection the browser will use for that request.241// We want these to be aligned across preloads and resources because otherwise the preload242// will be wasted.243// We investigated whether referrerPolicy should be included here but from experimentation244// it seems that browsers do not treat this as part of the http cache key and does not affect245// which connection is used.246type PreloadedWithCredentials = [247  /* crossOrigin */ ?CrossOriginEnum,248  /* integrity */ ?string,249];250251const EXISTS: Exists = null;252// This constant is to mark preloads that have no unique credentials253// to convey. It should never be checked by identity and we should not254// assume Preload values in ResumableState equal this value because they255// will have come from some parsed input.256const PRELOAD_NO_CREDS: Preloaded = [];257if (__DEV__) {258  Object.freeze(PRELOAD_NO_CREDS);259}260261// Per response, global state that is not contextual to the rendering subtree.262// This is resumable and therefore should be serializable.263export type ResumableState = {264  idPrefix: string,265  nextFormID: number,266  streamingFormat: StreamingFormat,267268  // We carry the bootstrap intializers in resumable state in case we postpone in the shell269  // of a prerender. On resume we will reinitialize the bootstrap scripts if necessary.270  // If we end up flushing the bootstrap scripts we void these on the resumable state271  bootstrapScriptContent?: string | void,272  bootstrapScripts?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,273  bootstrapModules?: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,274275  // state for script streaming format, unused if using external runtime / data276  instructions: InstructionState,277278  // postamble state279  hasBody: boolean,280  hasHtml: boolean,281282  // Resources - Request local cache283  unknownResources: {284    [asType: string]: {285      [href: string]: Preloaded,286    },287  },288  dnsResources: {[key: string]: Exists},289  connectResources: {290    default: {[key: string]: Exists},291    anonymous: {[key: string]: Exists},292    credentials: {[key: string]: Exists},293  },294  imageResources: {295    [key: string]: Preloaded,296  },297  styleResources: {298    [key: string]: Exists | Preloaded | PreloadedWithCredentials,299  },300  scriptResources: {301    [key: string]: Exists | Preloaded | PreloadedWithCredentials,302  },303  moduleUnknownResources: {304    [asType: string]: {305      [href: string]: Preloaded,306    },307  },308  moduleScriptResources: {309    [key: string]: Exists | Preloaded | PreloadedWithCredentials,310  },311};312313let currentlyFlushingRenderState: RenderState | null = null;314315const dataElementQuotedEnd = stringToPrecomputedChunk('"></template>');316317const startInlineScript = stringToPrecomputedChunk('<script');318const endInlineScript = stringToPrecomputedChunk('</script>');319320const startScriptSrc = stringToPrecomputedChunk('<script src="');321const startModuleSrc = stringToPrecomputedChunk('<script type="module" src="');322const scriptNonce = stringToPrecomputedChunk(' nonce="');323const scriptIntegirty = stringToPrecomputedChunk(' integrity="');324const scriptCrossOrigin = stringToPrecomputedChunk(' crossorigin="');325const endAsyncScript = stringToPrecomputedChunk(' async=""></script>');326327const startInlineStyle = stringToPrecomputedChunk('<style');328329/**330 * This escaping function is designed to work with with inline scripts where the entire331 * contents are escaped. Because we know we are escaping the entire script we can avoid for instance332 * escaping html comment string sequences that are valid javascript as well because333 * if there are no sebsequent <script sequences the html parser will never enter334 * script data double escaped state (see: https://www.w3.org/TR/html53/syntax.html#script-data-double-escaped-state)335 *336 * While untrusted script content should be made safe before using this api it will337 * ensure that the script cannot be early terminated or never terminated state338 */339function escapeEntireInlineScriptContent(scriptText: string) {340  if (__DEV__) {341    checkHtmlStringCoercion(scriptText);342  }343  return ('' + scriptText).replace(scriptRegex, scriptReplacer);344}345const scriptRegex = /(<\/|<)(s)(cript)/gi;346const scriptReplacer = (347  match: string,348  prefix: string,349  s: string,350  suffix: string,351) => `${prefix}${s === 's' ? '\\u0073' : '\\u0053'}${suffix}`;352353export type BootstrapScriptDescriptor = {354  src: string,355  integrity?: string,356  crossOrigin?: string,357};358export type ExternalRuntimeScript = {359  src: string,360  chunks: Array<Chunk | PrecomputedChunk>,361};362363const importMapScriptStart = stringToPrecomputedChunk(364  '<script type="importmap">',365);366const importMapScriptEnd = stringToPrecomputedChunk('</script>');367368// Since we store headers as strings we deal with their length in utf16 code units369// rather than visual characters or the utf8 encoding that is used for most binary370// serialization. Some common HTTP servers only allow for headers to be 4kB in length.371// We choose a default length that is likely to be well under this already limited length however372// pathological cases may still cause the utf-8 encoding of the headers to approach this limit.373// It should also be noted that this maximum is a soft maximum. we have not reached the limit we will374// allow one more header to be captured which means in practice if the limit is approached it will be exceeded375const DEFAULT_HEADERS_CAPACITY_IN_UTF16_CODE_UNITS = 2000;376377let didWarnForNewBooleanPropsWithEmptyValue: {[string]: boolean};378if (__DEV__) {379  didWarnForNewBooleanPropsWithEmptyValue = {};380}381382// Allows us to keep track of what we've already written so we can refer back to it.383// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag384// is set, the server will send instructions via data attributes (instead of inline scripts)385export function createRenderState(386  resumableState: ResumableState,387  nonce:388    | string389    | {390        script?: string,391        style?: string,392      }393    | void,394  externalRuntimeConfig: string | BootstrapScriptDescriptor | void,395  importMap: ImportMap | void,396  onHeaders: void | ((headers: HeadersDescriptor) => void),397  maxHeadersLength: void | number,398): RenderState {399  const nonceScript = typeof nonce === 'string' ? nonce : nonce && nonce.script;400  const inlineScriptWithNonce =401    nonceScript === undefined402      ? startInlineScript403      : stringToPrecomputedChunk(404          '<script nonce="' + escapeTextForBrowser(nonceScript) + '"',405        );406  const nonceStyle =407    typeof nonce === 'string' ? undefined : nonce && nonce.style;408  const inlineStyleWithNonce =409    nonceStyle === undefined410      ? startInlineStyle411      : stringToPrecomputedChunk(412          '<style nonce="' + escapeTextForBrowser(nonceStyle) + '"',413        );414  const idPrefix = resumableState.idPrefix;415416  const bootstrapChunks: Array<Chunk | PrecomputedChunk> = [];417  let externalRuntimeScript: null | ExternalRuntimeScript = null;418  const {bootstrapScriptContent, bootstrapScripts, bootstrapModules} =419    resumableState;420  if (bootstrapScriptContent !== undefined) {421    bootstrapChunks.push(inlineScriptWithNonce);422    pushCompletedShellIdAttribute(bootstrapChunks, resumableState);423    bootstrapChunks.push(424      endOfStartTag,425      stringToChunk(escapeEntireInlineScriptContent(bootstrapScriptContent)),426      endInlineScript,427    );428  }429  if (enableFizzExternalRuntime) {430    if (externalRuntimeConfig !== undefined) {431      if (typeof externalRuntimeConfig === 'string') {432        externalRuntimeScript = {433          src: externalRuntimeConfig,434          chunks: [],435        };436        pushScriptImpl(externalRuntimeScript.chunks, {437          src: externalRuntimeConfig,438          async: true,439          integrity: undefined,440          nonce: nonceScript,441        });442      } else {443        externalRuntimeScript = {444          src: externalRuntimeConfig.src,445          chunks: [],446        };447        pushScriptImpl(externalRuntimeScript.chunks, {448          src: externalRuntimeConfig.src,449          async: true,450          integrity: externalRuntimeConfig.integrity,451          nonce: nonceScript,452        });453      }454    }455  }456457  const importMapChunks: Array<Chunk | PrecomputedChunk> = [];458  if (importMap !== undefined) {459    const map = importMap;460    importMapChunks.push(importMapScriptStart);461    importMapChunks.push(462      stringToChunk(escapeEntireInlineScriptContent(JSON.stringify(map))),463    );464    importMapChunks.push(importMapScriptEnd);465  }466  if (__DEV__) {467    if (onHeaders && typeof maxHeadersLength === 'number') {468      if (maxHeadersLength <= 0) {469        console.error(470          'React expected a positive non-zero `maxHeadersLength` option but found %s instead. When using the `onHeaders` option you may supply an optional `maxHeadersLength` option as well however, when setting this value to zero or less no headers will be captured.',471          maxHeadersLength === 0 ? 'zero' : maxHeadersLength,472        );473      }474    }475  }476  const headers = onHeaders477    ? {478        preconnects: '',479        fontPreloads: '',480        highImagePreloads: '',481        remainingCapacity:482          // We seed the remainingCapacity with 2 extra bytes because when we decrement the capacity483          // we always assume we are inserting an interstitial ", " however the first header does not actually484          // consume these two extra bytes.485          2 +486          (typeof maxHeadersLength === 'number'487            ? maxHeadersLength488            : DEFAULT_HEADERS_CAPACITY_IN_UTF16_CODE_UNITS),489      }490    : null;491  const renderState: RenderState = {492    placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),493    segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),494    boundaryPrefix: stringToPrecomputedChunk(idPrefix + 'B:'),495    startInlineScript: inlineScriptWithNonce,496    startInlineStyle: inlineStyleWithNonce,497    preamble: createPreambleState(),498499    externalRuntimeScript: externalRuntimeScript,500    bootstrapChunks: bootstrapChunks,501    importMapChunks,502503    onHeaders,504    headers,505    resets: {506      font: {},507      dns: {},508      connect: {509        default: {},510        anonymous: {},511        credentials: {},512      },513      image: {},514      style: {},515    },516517    charsetChunks: [],518    viewportChunks: [],519    hoistableChunks: [],520521    // cleared on flush522    preconnects: new Set(),523    fontPreloads: new Set(),524    highImagePreloads: new Set(),525    // usedImagePreloads: new Set(),526    styles: new Map(),527    bootstrapScripts: new Set(),528    scripts: new Set(),529    bulkPreloads: new Set(),530531    preloads: {532      images: new Map(),533      stylesheets: new Map(),534      scripts: new Map(),535      moduleScripts: new Map(),536    },537538    nonce: {539      script: nonceScript,540      style: nonceStyle,541    },542    // like a module global for currently rendering boundary543    hoistableState: null,544    stylesToHoist: false,545  };546547  if (bootstrapScripts !== undefined) {548    for (let i = 0; i < bootstrapScripts.length; i++) {549      const scriptConfig = bootstrapScripts[i];550      let src, crossOrigin, integrity;551      const props: PreloadAsProps = {552        rel: 'preload',553        as: 'script',554        fetchPriority: 'low',555        nonce,556      } as any;557      if (typeof scriptConfig === 'string') {558        props.href = src = scriptConfig;559      } else {560        props.href = src = scriptConfig.src;561        props.integrity = integrity =562          typeof scriptConfig.integrity === 'string'563            ? scriptConfig.integrity564            : undefined;565        props.crossOrigin = crossOrigin =566          typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null567            ? undefined568            : scriptConfig.crossOrigin === 'use-credentials'569              ? 'use-credentials'570              : '';571      }572573      preloadBootstrapScriptOrModule(resumableState, renderState, src, props);574575      bootstrapChunks.push(576        startScriptSrc,577        stringToChunk(escapeTextForBrowser(src)),578        attributeEnd,579      );580      if (nonceScript) {581        bootstrapChunks.push(582          scriptNonce,583          stringToChunk(escapeTextForBrowser(nonceScript)),584          attributeEnd,585        );586      }587      if (typeof integrity === 'string') {588        bootstrapChunks.push(589          scriptIntegirty,590          stringToChunk(escapeTextForBrowser(integrity)),591          attributeEnd,592        );593      }594      if (typeof crossOrigin === 'string') {595        bootstrapChunks.push(596          scriptCrossOrigin,597          stringToChunk(escapeTextForBrowser(crossOrigin)),598          attributeEnd,599        );600      }601      pushCompletedShellIdAttribute(bootstrapChunks, resumableState);602      bootstrapChunks.push(endAsyncScript);603    }604  }605  if (bootstrapModules !== undefined) {606    for (let i = 0; i < bootstrapModules.length; i++) {607      const scriptConfig = bootstrapModules[i];608      let src, crossOrigin, integrity;609      const props: PreloadModuleProps = {610        rel: 'modulepreload',611        fetchPriority: 'low',612        nonce: nonceScript,613      } as any;614      if (typeof scriptConfig === 'string') {615        props.href = src = scriptConfig;616      } else {617        props.href = src = scriptConfig.src;618        props.integrity = integrity =619          typeof scriptConfig.integrity === 'string'620            ? scriptConfig.integrity621            : undefined;622        props.crossOrigin = crossOrigin =623          typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null624            ? undefined625            : scriptConfig.crossOrigin === 'use-credentials'626              ? 'use-credentials'627              : '';628      }629630      preloadBootstrapScriptOrModule(resumableState, renderState, src, props);631632      bootstrapChunks.push(633        startModuleSrc,634        stringToChunk(escapeTextForBrowser(src)),635        attributeEnd,636      );637      if (nonceScript) {638        bootstrapChunks.push(639          scriptNonce,640          stringToChunk(escapeTextForBrowser(nonceScript)),641          attributeEnd,642        );643      }644      if (typeof integrity === 'string') {645        bootstrapChunks.push(646          scriptIntegirty,647          stringToChunk(escapeTextForBrowser(integrity)),648          attributeEnd,649        );650      }651      if (typeof crossOrigin === 'string') {652        bootstrapChunks.push(653          scriptCrossOrigin,654          stringToChunk(escapeTextForBrowser(crossOrigin)),655          attributeEnd,656        );657      }658      pushCompletedShellIdAttribute(bootstrapChunks, resumableState);659      bootstrapChunks.push(endAsyncScript);660    }661  }662663  return renderState;664}665666export function resumeRenderState(667  resumableState: ResumableState,668  nonce: NonceOption | void,669): RenderState {670  return createRenderState(671    resumableState,672    nonce,673    undefined,674    undefined,675    undefined,676    undefined,677  );678}679680export function createResumableState(681  identifierPrefix: string | void,682  externalRuntimeConfig: string | BootstrapScriptDescriptor | void,683  bootstrapScriptContent: string | void,684  bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,685  bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,686): ResumableState {687  const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;688689  let streamingFormat = ScriptStreamingFormat;690  if (enableFizzExternalRuntime) {691    if (externalRuntimeConfig !== undefined) {692      streamingFormat = DataStreamingFormat;693    }694  }695  return {696    idPrefix: idPrefix,697    nextFormID: 0,698    streamingFormat,699    bootstrapScriptContent,700    bootstrapScripts,701    bootstrapModules,702    instructions: NothingSent,703    hasBody: false,704    hasHtml: false,705706    // @TODO add bootstrap script to implicit preloads707708    // persistent709    unknownResources: {},710    dnsResources: {},711    connectResources: {712      default: {},713      anonymous: {},714      credentials: {},715    },716    imageResources: {},717    styleResources: {},718    scriptResources: {},719    moduleUnknownResources: {},720    moduleScriptResources: {},721  };722}723724export function resetResumableState(725  resumableState: ResumableState,726  renderState: RenderState,727): void {728  // Resets the resumable state based on what didn't manage to fully flush in the render state.729  // This currently assumes nothing was flushed.730  resumableState.nextFormID = 0;731  resumableState.hasBody = false;732  resumableState.hasHtml = false;733  resumableState.unknownResources = {734    font: renderState.resets.font,735  };736  resumableState.dnsResources = renderState.resets.dns;737  resumableState.connectResources = renderState.resets.connect;738  resumableState.imageResources = renderState.resets.image;739  resumableState.styleResources = renderState.resets.style;740  resumableState.scriptResources = {};741  resumableState.moduleUnknownResources = {};742  resumableState.moduleScriptResources = {};743  resumableState.instructions = NothingSent; // Nothing was flushed so no instructions could've flushed.744}745746export function completeResumableState(resumableState: ResumableState): void {747  // This function is called when we have completed a prerender and there is a shell.748  resumableState.bootstrapScriptContent = undefined;749  resumableState.bootstrapScripts = undefined;750  resumableState.bootstrapModules = undefined;751}752753export type PreambleState = {754  htmlChunks: null | Array<Chunk | PrecomputedChunk>,755  headChunks: null | Array<Chunk | PrecomputedChunk>,756  bodyChunks: null | Array<Chunk | PrecomputedChunk>,757};758export function createPreambleState(): PreambleState {759  return {760    htmlChunks: null,761    headChunks: null,762    bodyChunks: null,763  };764}765766// Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion767// modes. We only include the variants as they matter for the sake of our purposes.768// We don't actually provide the namespace therefore we use constants instead of the string.769export const ROOT_HTML_MODE = 0; // Used for the root most element tag.770// We have a less than HTML_HTML_MODE check elsewhere. If you add more cases here, make sure it771// still makes sense772const HTML_HTML_MODE = 1; // Used for the <html> if it is at the top level.773const HTML_MODE = 2;774const HTML_HEAD_MODE = 3;775const SVG_MODE = 4;776const MATHML_MODE = 5;777const HTML_TABLE_MODE = 6;778const HTML_TABLE_BODY_MODE = 7;779const HTML_TABLE_ROW_MODE = 8;780const HTML_COLGROUP_MODE = 9;781// We have a greater than HTML_TABLE_MODE check elsewhere. If you add more cases here, make sure it782// still makes sense783784type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;785786const NO_SCOPE = /*         */ 0b0000000;787const NOSCRIPT_SCOPE = /*   */ 0b0000001;788const PICTURE_SCOPE = /*    */ 0b0000010;789const FALLBACK_SCOPE = /*   */ 0b0000100;790const EXIT_SCOPE = /*       */ 0b0001000; // A direct Instance below a Suspense fallback is the only thing that can "exit"791const ENTER_SCOPE = /*      */ 0b0010000; // A direct Instance below Suspense content is the only thing that can "enter"792const UPDATE_SCOPE = /*     */ 0b0100000; // Inside a scope that applies "update" ViewTransitions if anything mutates here.793const APPEARING_SCOPE = /*  */ 0b1000000; // Below Suspense content subtree which might appear in an "enter" animation or "shared" animation.794const PARENT_EXIT_SCOPE = /* */ 0b10000000; // Below a ViewTransition that is exiting.795const PARENT_ENTER_SCOPE = /* */ 0b100000000; // Below a ViewTransition that is entering.796797// Everything not listed here are tracked for the whole subtree as opposed to just798// until the next Instance.799const SUBTREE_SCOPE = ~(ENTER_SCOPE | EXIT_SCOPE);800801type ViewTransitionContext = {802  update: 'none' | 'auto' | string,803  enter: 'none' | 'auto' | string,804  exit: 'none' | 'auto' | string,805  share: 'none' | 'auto' | string,806  parentEnter: 'none' | 'auto' | string,807  parentExit: 'none' | 'auto' | string,808  name: 'auto' | string,809  autoName: string, // a name that can be used if an explicit one is not defined.810  nameIdx: number, // keeps track of how many duplicates of this name we've emitted.811};812813// Lets us keep track of contextual state and pick it back up after suspending.814export type FormatContext = {815  insertionMode: InsertionMode, // root/svg/html/mathml/table816  selectedValue: null | string | Array<string>, // the selected value(s) inside a <select>, or null outside <select>817  tagScope: number,818  viewTransition: null | ViewTransitionContext, // tracks if we're inside a ViewTransition outside the first DOM node819};820821function createFormatContext(822  insertionMode: InsertionMode,823  selectedValue: null | string | Array<string>,824  tagScope: number,825  viewTransition: null | ViewTransitionContext,826): FormatContext {827  return {828    insertionMode,829    selectedValue,830    tagScope,831    viewTransition,832  };833}834835export function canHavePreamble(formatContext: FormatContext): boolean {836  return formatContext.insertionMode < HTML_MODE;837}838839export function createRootFormatContext(namespaceURI?: string): FormatContext {840  const insertionMode =841    namespaceURI === 'http://www.w3.org/2000/svg'842      ? SVG_MODE843      : namespaceURI === 'http://www.w3.org/1998/Math/MathML'844        ? MATHML_MODE845        : ROOT_HTML_MODE;846  return createFormatContext(insertionMode, null, NO_SCOPE, null);847}848849export function getChildFormatContext(850  parentContext: FormatContext,851  type: string,852  props: Object,853): FormatContext {854  const subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;855  switch (type) {856    case 'noscript':857      return createFormatContext(858        HTML_MODE,859        null,860        subtreeScope | NOSCRIPT_SCOPE,861        null,862      );863    case 'select':864      return createFormatContext(865        HTML_MODE,866        props.value != null ? props.value : props.defaultValue,867        subtreeScope,868        null,869      );870    case 'svg':871      return createFormatContext(SVG_MODE, null, subtreeScope, null);872    case 'picture':873      return createFormatContext(874        HTML_MODE,875        null,876        subtreeScope | PICTURE_SCOPE,877        null,878      );879    case 'math':880      return createFormatContext(MATHML_MODE, null, subtreeScope, null);881    case 'foreignObject':882      return createFormatContext(HTML_MODE, null, subtreeScope, null);883    // Table parents are special in that their children can only be created at all if they're884    // wrapped in a table parent. So we need to encode that we're entering this mode.885    case 'table':886      return createFormatContext(HTML_TABLE_MODE, null, subtreeScope, null);887    case 'thead':888    case 'tbody':889    case 'tfoot':890      return createFormatContext(891        HTML_TABLE_BODY_MODE,892        null,893        subtreeScope,894        null,895      );896    case 'colgroup':897      return createFormatContext(HTML_COLGROUP_MODE, null, subtreeScope, null);898    case 'tr':899      return createFormatContext(HTML_TABLE_ROW_MODE, null, subtreeScope, null);900    case 'head':901      if (parentContext.insertionMode < HTML_MODE) {902        // We are either at the root or inside the <html> tag and can enter903        // the <head> scope904        return createFormatContext(HTML_HEAD_MODE, null, subtreeScope, null);905      }906      break;907    case 'html':908      if (parentContext.insertionMode === ROOT_HTML_MODE) {909        return createFormatContext(HTML_HTML_MODE, null, subtreeScope, null);910      }911      break;912  }913  if (parentContext.insertionMode >= HTML_TABLE_MODE) {914    // Whatever tag this was, it wasn't a table parent or other special parent, so we must have915    // entered plain HTML again.916    return createFormatContext(HTML_MODE, null, subtreeScope, null);917  }918  if (parentContext.insertionMode < HTML_MODE) {919    return createFormatContext(HTML_MODE, null, subtreeScope, null);920  }921  if (enableViewTransition) {922    if (parentContext.viewTransition !== null) {923      // If we're inside a view transition, regardless what element we were in, it consumes924      // the view transition context.925      return createFormatContext(926        parentContext.insertionMode,927        parentContext.selectedValue,928        subtreeScope,929        null,930      );931    }932  }933  if (parentContext.tagScope !== subtreeScope) {934    return createFormatContext(935      parentContext.insertionMode,936      parentContext.selectedValue,937      subtreeScope,938      null,939    );940  }941  return parentContext;942}943944function getSuspenseViewTransition(945  parentViewTransition: null | ViewTransitionContext,946): null | ViewTransitionContext {947  if (parentViewTransition === null) {948    return null;949  }950  // If a ViewTransition wraps a Suspense boundary it applies to the children Instances951  // in both the fallback and the content.952  // Since we only have a representation of ViewTransitions on the Instances themselves953  // we cannot model the parent ViewTransition activating "enter", "exit" or "share"954  // since those would be ambiguous with the Suspense boundary changing states and955  // affecting the same Instances.956  // We also can't model an "update" when that update is fallback nodes swapping for957  // content nodes. However, we can model is as a "share" from the fallback nodes to958  // the content nodes using the same name. We just have to assign the same name that959  // we would've used (the parent ViewTransition name or auto-assign one).960  const viewTransition: ViewTransitionContext = {961    update: parentViewTransition.update, // For deep updates.962    enter: 'none',963    exit: 'none',964    share: parentViewTransition.update, // For exit or enter of reveals.965    parentEnter: 'none',966    parentExit: 'none',967    name: parentViewTransition.autoName,968    autoName: parentViewTransition.autoName,969    // TOOD: If we have more than just this Suspense boundary as a child of the ViewTransition970    // then the parent needs to isolate the names so that they don't conflict.971    nameIdx: 0,972  };973  return viewTransition;974}975976export function getSuspenseFallbackFormatContext(977  resumableState: ResumableState,978  parentContext: FormatContext,979): FormatContext {980  if (parentContext.tagScope & UPDATE_SCOPE) {981    // If we're rendering a Suspense in fallback mode and that is inside a ViewTransition,982    // which hasn't disabled updates, then revealing it might animate the parent so we need983    // the ViewTransition instructions.984    resumableState.instructions |= NeedUpgradeToViewTransitions;985  }986  return createFormatContext(987    parentContext.insertionMode,988    parentContext.selectedValue,989    parentContext.tagScope | FALLBACK_SCOPE | EXIT_SCOPE,990    getSuspenseViewTransition(parentContext.viewTransition),991  );992}993994export function getSuspenseContentFormatContext(995  resumableState: ResumableState,996  parentContext: FormatContext,997): FormatContext {998  const viewTransition = getSuspenseViewTransition(999    parentContext.viewTransition,1000  );1001  let subtreeScope = parentContext.tagScope | ENTER_SCOPE;1002  if (viewTransition !== null && viewTransition.share !== 'none') {1003    // If we have a ViewTransition wrapping Suspense then the appearing animation1004    // will be applied just like an "enter" below. Mark it as animating.1005    subtreeScope |= APPEARING_SCOPE;1006  }1007  return createFormatContext(1008    parentContext.insertionMode,1009    parentContext.selectedValue,1010    subtreeScope,1011    viewTransition,1012  );1013}10141015export function getViewTransitionFormatContext(1016  resumableState: ResumableState,1017  parentContext: FormatContext,1018  update: ?string,1019  enter: ?string,1020  exit: ?string,1021  share: ?string,1022  parentEnter: ?string,1023  parentExit: ?string,1024  hasParentEnterHandler: boolean,1025  hasParentExitHandler: boolean,1026  name: ?string,1027  autoName: string, // name or an autogenerated unique name1028): FormatContext {1029  // We're entering a <ViewTransition>. Normalize props.1030  if (update == null) {1031    update = 'auto';1032  }1033  if (enter == null) {1034    enter = 'auto';1035  }1036  if (exit == null) {1037    exit = 'auto';1038  }1039  if (name == null) {1040    const parentViewTransition = parentContext.viewTransition;1041    if (parentViewTransition !== null) {1042      // If we have multiple nested ViewTransition and the parent has a "share"1043      // but the child doesn't, then the parent ViewTransition can still activate1044      // a share scenario so we reuse the name and share from the parent.1045      name = parentViewTransition.name;1046      share = parentViewTransition.share;1047    } else {1048      name = 'auto';1049      share = 'none'; // share is only relevant if there's an explicit name1050    }1051  } else {1052    if (share == null) {1053      share = 'auto';1054    }1055    if (parentContext.tagScope & FALLBACK_SCOPE) {1056      // If we have an explicit name and share is not disabled, and we're inside1057      // a fallback, then that fallback might pair with content and so we might need1058      // the ViewTransition instructions to animate between them.1059      resumableState.instructions |= NeedUpgradeToViewTransitions;1060    }1061  }1062  if (!(parentContext.tagScope & EXIT_SCOPE)) {1063    exit = 'none'; // exit is only relevant for the first ViewTransition inside fallback1064  } else {1065    resumableState.instructions |= NeedUpgradeToViewTransitions;1066  }1067  if (!(parentContext.tagScope & ENTER_SCOPE)) {1068    enter = 'none'; // enter is only relevant for the first ViewTransition inside content1069  } else {1070    resumableState.instructions |= NeedUpgradeToViewTransitions;1071  }1072  let resolvedParentEnter = 'none';1073  let resolvedParentExit = 'none';1074  if (enableViewTransitionParentEnterExit) {1075    if (1076      parentEnter != null &&1077      (parentContext.tagScope & PARENT_ENTER_SCOPE) !== 01078    ) {1079      resolvedParentEnter = parentEnter;1080    }1081    if (1082      parentExit != null &&1083      (parentContext.tagScope & PARENT_EXIT_SCOPE) !== 01084    ) {1085      resolvedParentExit = parentExit;1086    }1087  }1088  const viewTransition: ViewTransitionContext = {1089    update,1090    enter,1091    exit,1092    share,1093    parentEnter: resolvedParentEnter,1094    parentExit: resolvedParentExit,1095    name,1096    autoName,1097    nameIdx: 0,1098  };1099  let subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;1100  if (update !== 'none') {1101    subtreeScope |= UPDATE_SCOPE;1102  } else {1103    subtreeScope &= ~UPDATE_SCOPE;1104  }1105  if (enter !== 'none') {1106    subtreeScope |= APPEARING_SCOPE;1107  }1108  if (enableViewTransitionParentEnterExit) {1109    // Parent enter relay: a ViewTransition that is itself entering starts a relay1110    // for its subtree. A nested ViewTransition continues that relay as long as it1111    // opts in with a parentEnter prop that doesn't resolve to "none", or with an1112    // onParentEnter handler. A missing parentEnter (with no handler) or an1113    // explicit "none" stops the relay so descendants below it don't participate.1114    // This mirrors commitParentEnterViewTransitions.1115    if (enter !== 'none') {1116      subtreeScope |= PARENT_ENTER_SCOPE;1117    } else if (1118      (parentContext.tagScope & PARENT_ENTER_SCOPE) !== 0 &&1119      (parentEnter === 'none' ||1120        (parentEnter === undefined && !hasParentEnterHandler))1121    ) {1122      subtreeScope &= ~PARENT_ENTER_SCOPE;1123    }1124    // Parent exit relay: mirror of the enter relay above.1125    if (exit !== 'none') {1126      subtreeScope |= PARENT_EXIT_SCOPE;1127    } else if (1128      (parentContext.tagScope & PARENT_EXIT_SCOPE) !== 0 &&1129      (parentExit === 'none' ||1130        (parentExit === undefined && !hasParentExitHandler))1131    ) {1132      subtreeScope &= ~PARENT_EXIT_SCOPE;1133    }1134  }1135  return createFormatContext(1136    parentContext.insertionMode,1137    parentContext.selectedValue,1138    subtreeScope,1139    viewTransition,1140  );1141}11421143export function isPreambleContext(formatContext: FormatContext): boolean {1144  return formatContext.insertionMode === HTML_HEAD_MODE;1145}11461147export function makeId(1148  resumableState: ResumableState,1149  treeId: string,1150  localId: number,1151): string {1152  const idPrefix = resumableState.idPrefix;11531154  let id = '_' + idPrefix + 'R_' + treeId;11551156  // Unless this is the first id at this level, append a number at the end1157  // that represents the position of this useId hook among all the useId1158  // hooks for this fiber.1159  if (localId > 0) {1160    id += 'H' + localId.toString(32);1161  }11621163  return id + '_';1164}11651166function encodeHTMLTextNode(text: string): string {1167  return escapeTextForBrowser(text);1168}11691170const textSeparator = stringToPrecomputedChunk('<!-- -->');11711172export function pushTextInstance(1173  target: Array<Chunk | PrecomputedChunk>,1174  text: string,1175  renderState: RenderState,1176  textEmbedded: boolean,1177): boolean {1178  if (text === '') {1179    // Empty text doesn't have a DOM node representation and the hydration is aware of this.1180    return textEmbedded;1181  }1182  if (textEmbedded) {1183    target.push(textSeparator);1184  }1185  target.push(stringToChunk(encodeHTMLTextNode(text)));1186  return true;1187}11881189// Called when Fizz is done with a Segment. Currently the only purpose is to conditionally1190// emit a text separator when we don't know for sure it is safe to omit1191export function pushSegmentFinale(1192  target: Array<Chunk | PrecomputedChunk>,1193  renderState: RenderState,1194  lastPushedText: boolean,1195  textEmbedded: boolean,1196): void {1197  if (lastPushedText && textEmbedded) {1198    target.push(textSeparator);1199  }1200}12011202function pushViewTransitionAttributes(1203  target: Array<Chunk | PrecomputedChunk>,1204  formatContext: FormatContext,1205): void {1206  if (!enableViewTransition) {1207    return;1208  }1209  const viewTransition = formatContext.viewTransition;1210  if (viewTransition === null) {1211    return;1212  }1213  if (viewTransition.name !== 'auto') {1214    pushStringAttribute(1215      target,1216      'vt-name',1217      viewTransition.nameIdx === 01218        ? viewTransition.name1219        : viewTransition.name + '_' + viewTransition.nameIdx,1220    );1221    // Increment the index in case we have multiple children to the same ViewTransition.1222    // Because this is a side-effect in render, we should ideally call pushViewTransitionAttributes1223    // after we've suspended (like forms do), so that we don't increment each attempt.1224    // TODO: Make this deterministic.1225    viewTransition.nameIdx++;1226  }1227  pushStringAttribute(target, 'vt-update', viewTransition.update);1228  if (viewTransition.enter !== 'none') {1229    pushStringAttribute(target, 'vt-enter', viewTransition.enter);1230  }1231  if (viewTransition.exit !== 'none') {1232    pushStringAttribute(target, 'vt-exit', viewTransition.exit);1233  }1234  if (viewTransition.share !== 'none') {1235    pushStringAttribute(target, 'vt-share', viewTransition.share);1236  }1237  if (1238    enableViewTransitionParentEnterExit &&1239    viewTransition.parentEnter !== 'none'1240  ) {1241    pushStringAttribute(target, 'vt-parent-enter', viewTransition.parentEnter);1242  }1243  if (1244    enableViewTransitionParentEnterExit &&1245    viewTransition.parentExit !== 'none'1246  ) {1247    pushStringAttribute(target, 'vt-parent-exit', viewTransition.parentExit);1248  }1249}12501251const styleNameCache: Map<string, PrecomputedChunk> = new Map();1252function processStyleName(styleName: string): PrecomputedChunk {1253  const chunk = styleNameCache.get(styleName);1254  if (chunk !== undefined) {1255    return chunk;1256  }1257  const result = stringToPrecomputedChunk(1258    escapeTextForBrowser(hyphenateStyleName(styleName)),1259  );1260  styleNameCache.set(styleName, result);1261  return result;1262}12631264const styleAttributeStart = stringToPrecomputedChunk(' style="');1265const styleAssign = stringToPrecomputedChunk(':');1266const styleSeparator = stringToPrecomputedChunk(';');12671268function pushStyleAttribute(1269  target: Array<Chunk | PrecomputedChunk>,1270  style: Object,1271): void {1272  if (typeof style !== 'object') {1273    throw new Error(1274      'The `style` prop expects a mapping from style properties to values, ' +1275        "not a string. For example, style={{marginRight: spacing + 'em'}} when " +1276        'using JSX.',1277    );1278  }12791280  let isFirst = true;1281  for (const styleName in style) {1282    if (!hasOwnProperty.call(style, styleName)) {1283      continue;1284    }1285    // If you provide unsafe user data here they can inject arbitrary CSS1286    // which may be problematic (I couldn't repro this):1287    // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet1288    // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/1289    // This is not an XSS hole but instead a potential CSS injection issue1290    // which has lead to a greater discussion about how we're going to1291    // trust URLs moving forward. See #21159011292    const styleValue = style[styleName];1293    if (1294      styleValue == null ||1295      typeof styleValue === 'boolean' ||1296      styleValue === ''1297    ) {1298      // TODO: We used to set empty string as a style with an empty value. Does that ever make sense?1299      continue;1300    }13011302    let nameChunk;1303    let valueChunk;1304    const isCustomProperty = styleName.indexOf('--') === 0;1305    if (isCustomProperty) {1306      nameChunk = stringToChunk(escapeTextForBrowser(styleName));1307      if (__DEV__) {1308        checkCSSPropertyStringCoercion(styleValue, styleName);1309      }1310      valueChunk = stringToChunk(1311        escapeTextForBrowser(('' + styleValue).trim()),1312      );1313    } else {1314      if (__DEV__) {1315        warnValidStyle(styleName, styleValue);1316      }13171318      nameChunk = processStyleName(styleName);1319      if (typeof styleValue === 'number') {1320        if (styleValue !== 0 && !isUnitlessNumber(styleName)) {1321          valueChunk = stringToChunk(styleValue + 'px'); // Presumes implicit 'px' suffix for unitless numbers1322        } else {1323          valueChunk = stringToChunk('' + styleValue);1324        }1325      } else {1326        if (__DEV__) {1327          checkCSSPropertyStringCoercion(styleValue, styleName);1328        }1329        valueChunk = stringToChunk(1330          escapeTextForBrowser(('' + styleValue).trim()),1331        );1332      }1333    }1334    if (isFirst) {1335      isFirst = false;1336      // If it's first, we don't need any separators prefixed.1337      target.push(styleAttributeStart, nameChunk, styleAssign, valueChunk);1338    } else {1339      target.push(styleSeparator, nameChunk, styleAssign, valueChunk);1340    }1341  }1342  if (!isFirst) {1343    target.push(attributeEnd);1344  }1345}13461347const attributeSeparator = stringToPrecomputedChunk(' ');1348const attributeAssign = stringToPrecomputedChunk('="');1349const attributeEnd = stringToPrecomputedChunk('"');1350const attributeEmptyString = stringToPrecomputedChunk('=""');13511352function pushBooleanAttribute(1353  target: Array<Chunk | PrecomputedChunk>,1354  name: string,1355  value: string | boolean | number | Function | Object, // not null or undefined1356): void {1357  if (value && typeof value !== 'function' && typeof value !== 'symbol') {1358    target.push(attributeSeparator, stringToChunk(name), attributeEmptyString);1359  }1360}13611362function pushStringAttribute(1363  target: Array<Chunk | PrecomputedChunk>,1364  name: string,1365  value: string | boolean | number | Function | Object, // not null or undefined1366): void {1367  if (1368    typeof value !== 'function' &&1369    typeof value !== 'symbol' &&1370    typeof value !== 'boolean'1371  ) {1372    target.push(1373      attributeSeparator,1374      stringToChunk(name),1375      attributeAssign,1376      stringToChunk(escapeTextForBrowser(value)),1377      attributeEnd,1378    );1379  }1380}13811382function makeFormFieldPrefix(resumableState: ResumableState): string {1383  // TODO: Make this deterministic.1384  const id = resumableState.nextFormID++;1385  return resumableState.idPrefix + id;1386}13871388// Since this will likely be repeated a lot in the HTML, we use a more concise message1389// than on the client and hopefully it's googleable.1390const actionJavaScriptURL = stringToPrecomputedChunk(1391  escapeTextForBrowser(1392    // eslint-disable-next-line no-script-url1393    "javascript:throw new Error('React form unexpectedly submitted.')",1394  ),1395);13961397const startHiddenInputChunk = stringToPrecomputedChunk('<input type="hidden"');13981399function pushAdditionalFormField(1400  this: Array<Chunk | PrecomputedChunk>,1401  value: string | File,1402  key: string,1403): void {1404  const target: Array<Chunk | PrecomputedChunk> = this;1405  target.push(startHiddenInputChunk);1406  validateAdditionalFormField(value, key);1407  pushStringAttribute(target, 'name', key);1408  pushStringAttribute(target, 'value', value);1409  target.push(endOfStartTagSelfClosing);1410}14111412function pushAdditionalFormFields(1413  target: Array<Chunk | PrecomputedChunk>,1414  formData: void | null | FormData,1415) {1416  if (formData != null) {1417    // $FlowFixMe[prop-missing]: FormData has forEach.1418    formData.forEach(pushAdditionalFormField, target);1419  }1420}14211422function validateAdditionalFormField(value: string | File, key: string): void {1423  if (typeof value !== 'string') {1424    throw new Error(1425      'File/Blob fields are not yet supported in progressive forms. ' +1426        'Will fallback to client hydration.',1427    );1428  }1429}14301431function validateAdditionalFormFields(formData: void | null | FormData) {1432  if (formData != null) {1433    // $FlowFixMe[prop-missing]: FormData has forEach.1434    formData.forEach(validateAdditionalFormField);1435  }1436  return formData;1437}14381439function getCustomFormFields(1440  resumableState: ResumableState,1441  formAction: any,1442): null | ReactCustomFormAction {1443  const customAction = formAction.$$FORM_ACTION;1444  if (typeof customAction === 'function') {1445    const prefix = makeFormFieldPrefix(resumableState);1446    try {1447      const customFields = formAction.$$FORM_ACTION(prefix);1448      if (customFields) {1449        validateAdditionalFormFields(customFields.data);1450      }1451      return customFields;1452    } catch (x) {1453      if (typeof x === 'object' && x !== null && typeof x.then === 'function') {1454        // Rethrow suspense.1455        throw x;1456      }1457      // If we fail to encode the form action for progressive enhancement for some reason,1458      // fallback to trying replaying on the client instead of failing the page. It might1459      // work there.1460      if (__DEV__) {1461        // TODO: Should this be some kind of recoverable error?1462        console.error(1463          'Failed to serialize an action for progressive enhancement:\n%s',1464          x,1465        );1466      }1467    }1468  }1469  return null;1470}14711472function pushFormActionAttribute(1473  target: Array<Chunk | PrecomputedChunk>,1474  resumableState: ResumableState,1475  renderState: RenderState,1476  formAction: any,1477  formEncType: any,1478  formMethod: any,1479  formTarget: any,1480  name: any,1481): void | null | FormData {1482  let formData = null;1483  if (typeof formAction === 'function') {1484    // Function form actions cannot control the form properties1485    if (__DEV__) {1486      if (name !== null && !didWarnFormActionName) {1487        didWarnFormActionName = true;1488        console.error(1489          'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +1490            'React needs it to encode which action should be invoked. It will get overridden.',1491        );1492      }1493      if (1494        (formEncType !== null || formMethod !== null) &&1495        !didWarnFormActionMethod1496      ) {1497        didWarnFormActionMethod = true;1498        console.error(1499          'Cannot specify a formEncType or formMethod for a button that specifies a ' +1500            'function as a formAction. React provides those automatically. They will get overridden.',1501        );1502      }1503      if (formTarget !== null && !didWarnFormActionTarget) {1504        didWarnFormActionTarget = true;1505        console.error(1506          'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +1507            'The function will always be executed in the same window.',1508        );1509      }1510    }1511    const customFields = getCustomFormFields(resumableState, formAction);1512    if (customFields !== null) {1513      // This action has a custom progressive enhancement form that can submit the form1514      // back to the server if it's invoked before hydration. Such as a Server Action.1515      name = customFields.name;1516      formAction = customFields.action || '';1517      formEncType = customFields.encType;1518      formMethod = customFields.method;1519      formTarget = customFields.target;1520      formData = customFields.data;1521    } else {1522      // Set a javascript URL that doesn't do anything. We don't expect this to be invoked1523      // because we'll preventDefault in the Fizz runtime, but it can happen if a form is1524      // manually submitted or if someone calls stopPropagation before React gets the event.1525      // If CSP is used to block javascript: URLs that's fine too. It just won't show this1526      // error message but the URL will be logged.1527      target.push(1528        attributeSeparator,1529        stringToChunk('formAction'),1530        attributeAssign,1531        actionJavaScriptURL,1532        attributeEnd,1533      );1534      name = null;1535      formAction = null;1536      formEncType = null;1537      formMethod = null;1538      formTarget = null;1539      injectFormReplayingRuntime(resumableState, renderState);1540    }1541  }1542  if (name != null) {1543    pushAttribute(target, 'name', name);1544  }1545  if (formAction != null) {1546    pushAttribute(target, 'formAction', formAction);1547  }1548  if (formEncType != null) {1549    pushAttribute(target, 'formEncType', formEncType);1550  }1551  if (formMethod != null) {1552    pushAttribute(target, 'formMethod', formMethod);1553  }1554  if (formTarget != null) {1555    pushAttribute(target, 'formTarget', formTarget);1556  }1557  return formData;1558}15591560let blobCache: null | WeakMap<Blob, Thenable<string>> = null;15611562function pushSrcObjectAttribute(1563  target: Array<Chunk | PrecomputedChunk>,1564  blob: Blob,1565): void {1566  // Throwing a Promise style suspense read of the Blob content.1567  if (blobCache === null) {1568    blobCache = new WeakMap();1569  }1570  const suspenseCache: WeakMap<Blob, Thenable<string>> = blobCache;1571  let thenable = suspenseCache.get(blob);1572  if (thenable === undefined) {1573    thenable = readAsDataURL(blob) as any as Thenable<string>;1574    thenable.then(1575      result => {1576        (thenable as any).status = 'fulfilled';1577        (thenable as any).value = result;1578      },1579      error => {1580        (thenable as any).status = 'rejected';1581        (thenable as any).reason = error;1582      },1583    );1584    suspenseCache.set(blob, thenable);1585  }1586  if (thenable.status === 'rejected') {1587    throw thenable.reason;1588  } else if (thenable.status !== 'fulfilled') {1589    throw thenable;1590  }1591  const url = thenable.value;1592  target.push(1593    attributeSeparator,1594    stringToChunk('src'),1595    attributeAssign,1596    stringToChunk(escapeTextForBrowser(url)),1597    attributeEnd,1598  );1599}16001601function pushAttribute(1602  target: Array<Chunk | PrecomputedChunk>,1603  name: string,1604  value: string | boolean | number | Function | Object, // not null or undefined1605): void {1606  switch (name) {1607    // These are very common props and therefore are in the beginning of the switch.1608    // TODO: aria-label is a very common prop but allows booleans so is not like the others1609    // but should ideally go in this list too.1610    case 'className': {1611      pushStringAttribute(target, 'class', value);1612      break;1613    }1614    case 'tabIndex': {1615      pushStringAttribute(target, 'tabindex', value);1616      break;1617    }1618    case 'dir':1619    case 'role':1620    case 'viewBox':1621    case 'width':1622    case 'height': {1623      pushStringAttribute(target, name, value);1624      break;1625    }1626    case 'style': {1627      pushStyleAttribute(target, value);1628      return;1629    }1630    case 'src': {1631      // $FlowFixMe[invalid-compare]1632      if (enableSrcObject && typeof value === 'object' && value !== null) {1633        if (typeof Blob === 'function' && value instanceof Blob) {1634          pushSrcObjectAttribute(target, value);1635          return;1636        }1637      }1638      // Fallthrough to general urls1639    }1640    case 'href': {1641      if (value === '') {1642        if (__DEV__) {1643          if (name === 'src') {1644            console.error(1645              'An empty string ("") was passed to the %s attribute. ' +1646                'This may cause the browser to download the whole page again over the network. ' +1647                'To fix this, either do not render the element at all ' +1648                'or pass null to %s instead of an empty string.',1649              name,1650              name,1651            );1652          } else {1653            console.error(1654              'An empty string ("") was passed to the %s attribute. ' +1655                'To fix this, either do not render the element at all ' +1656                'or pass null to %s instead of an empty string.',1657              name,1658              name,1659            );1660          }1661        }1662        return;1663      }1664    }1665    // Fall through to the last case which shouldn't remove empty strings.1666    case 'action':1667    case 'formAction': {1668      // TODO: Consider only special casing these for each tag.1669      if (1670        value == null ||1671        typeof value === 'function' ||1672        typeof value === 'symbol' ||1673        typeof value === 'boolean'1674      ) {1675        return;1676      }1677      if (__DEV__) {1678        checkAttributeStringCoercion(value, name);1679      }1680      const sanitizedValue = sanitizeURL('' + value);1681      target.push(1682        attributeSeparator,1683        stringToChunk(name),1684        attributeAssign,1685        stringToChunk(escapeTextForBrowser(sanitizedValue)),1686        attributeEnd,1687      );1688      return;1689    }1690    case 'defaultValue':1691    case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements.1692    case 'innerHTML': // Must use dangerouslySetInnerHTML instead.1693    case 'suppressContentEditableWarning':1694    case 'suppressHydrationWarning':1695    case 'ref':1696      // Ignored. These are built-in to React on the client.1697      return;1698    case 'autoFocus':1699    case 'multiple':1700    case 'muted': {1701      pushBooleanAttribute(target, name.toLowerCase(), value);1702      return;1703    }1704    case 'xlinkHref': {1705      if (1706        typeof value === 'function' ||1707        typeof value === 'symbol' ||1708        typeof value === 'boolean'1709      ) {1710        return;1711      }1712      if (__DEV__) {1713        checkAttributeStringCoercion(value, name);1714      }1715      const sanitizedValue = sanitizeURL('' + value);1716      target.push(1717        attributeSeparator,1718        stringToChunk('xlink:href'),1719        attributeAssign,1720        stringToChunk(escapeTextForBrowser(sanitizedValue)),1721        attributeEnd,1722      );1723      return;1724    }1725    case 'contentEditable':1726    case 'spellCheck':1727    case 'draggable':1728    case 'value':1729    case 'autoReverse':1730    case 'externalResourcesRequired':1731    case 'focusable':1732    case 'preserveAlpha': {1733      // Booleanish String1734      // These are "enumerated" attributes that accept "true" and "false".1735      // In React, we let users pass `true` and `false` even though technically1736      // these aren't boolean attributes (they are coerced to strings).1737      if (typeof value !== 'function' && typeof value !== 'symbol') {1738        target.push(1739          attributeSeparator,1740          stringToChunk(name),1741          attributeAssign,1742          stringToChunk(escapeTextForBrowser(value)),1743          attributeEnd,1744        );1745      }1746      return;1747    }1748    case 'inert': {1749      if (__DEV__) {1750        if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[name]) {1751          didWarnForNewBooleanPropsWithEmptyValue[name] = true;1752          console.error(1753            'Received an empty string for a boolean attribute `%s`. ' +1754              'This will treat the attribute as if it were false. ' +1755              'Either pass `false` to silence this warning, or ' +1756              'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',1757            name,1758          );1759        }1760      }1761    }1762    // Fallthrough for boolean props that don't have a warning for empty strings.1763    case 'allowFullScreen':1764    case 'async':1765    case 'autoPlay':1766    case 'controls':1767    case 'credentialless':1768    case 'default':1769    case 'defer':1770    case 'disabled':1771    case 'disablePictureInPicture':1772    case 'disableRemotePlayback':1773    case 'formNoValidate':1774    case 'hidden':1775    case 'loop':1776    case 'noModule':1777    case 'noValidate':1778    case 'open':1779    case 'playsInline':1780    case 'readOnly':1781    case 'required':1782    case 'reversed':1783    case 'scoped':1784    case 'seamless':1785    case 'itemScope': {1786      // Boolean1787      if (value && typeof value !== 'function' && typeof value !== 'symbol') {1788        target.push(1789          attributeSeparator,1790          stringToChunk(name),1791          attributeEmptyString,1792        );1793      }1794      return;1795    }1796    case 'capture':1797    case 'download': {1798      // Overloaded Boolean1799      if (value === true) {1800        target.push(1801          attributeSeparator,1802          stringToChunk(name),1803          attributeEmptyString,1804        );1805      } else if (value === false) {1806        // Ignored1807      } else if (typeof value !== 'function' && typeof value !== 'symbol') {1808        target.push(1809          attributeSeparator,1810          stringToChunk(name),1811          attributeAssign,1812          stringToChunk(escapeTextForBrowser(value)),1813          attributeEnd,1814        );1815      }1816      return;1817    }1818    case 'cols':1819    case 'rows':1820    case 'size':1821    case 'span': {1822      // These are HTML attributes that must be positive numbers.1823      if (1824        typeof value !== 'function' &&1825        typeof value !== 'symbol' &&1826        !isNaN(value) &&1827        (value as any) >= 11828      ) {1829        target.push(1830          attributeSeparator,1831          stringToChunk(name),1832          attributeAssign,1833          stringToChunk(escapeTextForBrowser(value)),1834          attributeEnd,1835        );1836      }1837      return;1838    }1839    case 'rowSpan':1840    case 'start': {1841      // These are HTML attributes that must be numbers.1842      if (1843        typeof value !== 'function' &&1844        typeof value !== 'symbol' &&1845        !isNaN(value)1846      ) {1847        target.push(1848          attributeSeparator,1849          stringToChunk(name),1850          attributeAssign,1851          stringToChunk(escapeTextForBrowser(value)),1852          attributeEnd,1853        );1854      }1855      return;1856    }1857    case 'xlinkActuate':1858      pushStringAttribute(target, 'xlink:actuate', value);1859      return;1860    case 'xlinkArcrole':1861      pushStringAttribute(target, 'xlink:arcrole', value);1862      return;1863    case 'xlinkRole':1864      pushStringAttribute(target, 'xlink:role', value);1865      return;1866    case 'xlinkShow':1867      pushStringAttribute(target, 'xlink:show', value);1868      return;1869    case 'xlinkTitle':1870      pushStringAttribute(target, 'xlink:title', value);1871      return;1872    case 'xlinkType':1873      pushStringAttribute(target, 'xlink:type', value);1874      return;1875    case 'xmlBase':1876      pushStringAttribute(target, 'xml:base', value);1877      return;1878    case 'xmlLang':1879      pushStringAttribute(target, 'xml:lang', value);1880      return;1881    case 'xmlSpace':1882      pushStringAttribute(target, 'xml:space', value);1883      return;1884    default:1885      if (1886        // shouldIgnoreAttribute1887        // We have already filtered out null/undefined and reserved words.1888        name.length > 2 &&1889        (name[0] === 'o' || name[0] === 'O') &&1890        (name[1] === 'n' || name[1] === 'N')1891      ) {1892        return;1893      }18941895      const attributeName = getAttributeAlias(name);1896      if (isAttributeNameSafe(attributeName)) {1897        // shouldRemoveAttribute1898        switch (typeof value) {1899          case 'function':1900          case 'symbol':1901            return;1902          case 'boolean': {1903            const prefix = attributeName.toLowerCase().slice(0, 5);1904            if (prefix !== 'data-' && prefix !== 'aria-') {1905              return;1906            }1907          }1908        }1909        target.push(1910          attributeSeparator,1911          stringToChunk(attributeName),1912          attributeAssign,1913          stringToChunk(escapeTextForBrowser(value)),1914          attributeEnd,1915        );1916      }1917  }1918}19191920const endOfStartTag = stringToPrecomputedChunk('>');1921const endOfStartTagSelfClosing = stringToPrecomputedChunk('/>');19221923function pushInnerHTML(1924  target: Array<Chunk | PrecomputedChunk>,1925  innerHTML: any,1926  children: any,1927) {1928  if (innerHTML != null) {1929    if (children != null) {1930      throw new Error(1931        'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',1932      );1933    }19341935    if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) {1936      throw new Error(1937        '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +1938          'Please visit https://react.dev/link/dangerously-set-inner-html ' +1939          'for more information.',1940      );1941    }19421943    const html = innerHTML.__html;1944    if (html !== null && html !== undefined) {1945      if (__DEV__) {1946        checkHtmlStringCoercion(html);1947      }1948      target.push(stringToChunk('' + html));1949    }1950  }1951}19521953// TODO: Move these to RenderState so that we warn for every request.1954// It would help debugging in stateful servers (e.g. service worker).1955let didWarnDefaultInputValue = false;1956let didWarnDefaultChecked = false;1957let didWarnDefaultSelectValue = false;1958let didWarnDefaultTextareaValue = false;1959let didWarnInvalidOptionChildren = false;1960let didWarnInvalidOptionInnerHTML = false;1961let didWarnSelectedSetOnOption = false;1962let didWarnFormActionType = false;1963let didWarnFormActionName = false;1964let didWarnFormActionTarget = false;1965let didWarnFormActionMethod = false;19661967function checkSelectProp(props: any, propName: string) {1968  if (__DEV__) {1969    const value = props[propName];1970    if (value != null) {1971      const array = isArray(value);1972      if (props.multiple && !array) {1973        console.error(1974          'The `%s` prop supplied to <select> must be an array if ' +1975            '`multiple` is true.',1976          propName,1977        );1978      } else if (!props.multiple && array) {1979        console.error(1980          'The `%s` prop supplied to <select> must be a scalar ' +1981            'value if `multiple` is false.',1982          propName,1983        );1984      }1985    }1986  }1987}19881989function pushStartAnchor(1990  target: Array<Chunk | PrecomputedChunk>,1991  props: Object,1992  formatContext: FormatContext,1993): ReactNodeList {1994  target.push(startChunkForTag('a'));19951996  let children = null;1997  let innerHTML = null;1998  for (const propKey in props) {1999    if (hasOwnProperty.call(props, propKey)) {2000      const propValue = props[propKey];

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.