packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js JAVASCRIPT 7,248 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 7,248.
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(461      nonceScript === undefined462        ? importMapScriptStart463        : stringToPrecomputedChunk(464            '<script type="importmap" nonce="' +465              escapeTextForBrowser(nonceScript) +466              '">',467          ),468    );469    importMapChunks.push(470      stringToChunk(escapeEntireInlineScriptContent(JSON.stringify(map))),471    );472    importMapChunks.push(importMapScriptEnd);473  }474  if (__DEV__) {475    if (onHeaders && typeof maxHeadersLength === 'number') {476      if (maxHeadersLength <= 0) {477        console.error(478          '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.',479          maxHeadersLength === 0 ? 'zero' : maxHeadersLength,480        );481      }482    }483  }484  const headers = onHeaders485    ? {486        preconnects: '',487        fontPreloads: '',488        highImagePreloads: '',489        remainingCapacity:490          // We seed the remainingCapacity with 2 extra bytes because when we decrement the capacity491          // we always assume we are inserting an interstitial ", " however the first header does not actually492          // consume these two extra bytes.493          2 +494          (typeof maxHeadersLength === 'number'495            ? maxHeadersLength496            : DEFAULT_HEADERS_CAPACITY_IN_UTF16_CODE_UNITS),497      }498    : null;499  const renderState: RenderState = {500    placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),501    segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),502    boundaryPrefix: stringToPrecomputedChunk(idPrefix + 'B:'),503    startInlineScript: inlineScriptWithNonce,504    startInlineStyle: inlineStyleWithNonce,505    preamble: createPreambleState(),506507    externalRuntimeScript: externalRuntimeScript,508    bootstrapChunks: bootstrapChunks,509    importMapChunks,510511    onHeaders,512    headers,513    resets: {514      font: {},515      dns: {},516      connect: {517        default: {},518        anonymous: {},519        credentials: {},520      },521      image: {},522      style: {},523    },524525    charsetChunks: [],526    viewportChunks: [],527    hoistableChunks: [],528529    // cleared on flush530    preconnects: new Set(),531    fontPreloads: new Set(),532    highImagePreloads: new Set(),533    // usedImagePreloads: new Set(),534    styles: new Map(),535    bootstrapScripts: new Set(),536    scripts: new Set(),537    bulkPreloads: new Set(),538539    preloads: {540      images: new Map(),541      stylesheets: new Map(),542      scripts: new Map(),543      moduleScripts: new Map(),544    },545546    nonce: {547      script: nonceScript,548      style: nonceStyle,549    },550    // like a module global for currently rendering boundary551    hoistableState: null,552    stylesToHoist: false,553  };554555  if (bootstrapScripts !== undefined) {556    for (let i = 0; i < bootstrapScripts.length; i++) {557      const scriptConfig = bootstrapScripts[i];558      let src, crossOrigin, integrity;559      const props: PreloadAsProps = {560        rel: 'preload',561        as: 'script',562        fetchPriority: 'low',563        nonce,564      } as any;565      if (typeof scriptConfig === 'string') {566        props.href = src = scriptConfig;567      } else {568        props.href = src = scriptConfig.src;569        props.integrity = integrity =570          typeof scriptConfig.integrity === 'string'571            ? scriptConfig.integrity572            : undefined;573        props.crossOrigin = crossOrigin =574          typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null575            ? undefined576            : scriptConfig.crossOrigin === 'use-credentials'577              ? 'use-credentials'578              : '';579      }580581      preloadBootstrapScriptOrModule(resumableState, renderState, src, props);582583      bootstrapChunks.push(584        startScriptSrc,585        stringToChunk(escapeTextForBrowser(src)),586        attributeEnd,587      );588      if (nonceScript) {589        bootstrapChunks.push(590          scriptNonce,591          stringToChunk(escapeTextForBrowser(nonceScript)),592          attributeEnd,593        );594      }595      if (typeof integrity === 'string') {596        bootstrapChunks.push(597          scriptIntegirty,598          stringToChunk(escapeTextForBrowser(integrity)),599          attributeEnd,600        );601      }602      if (typeof crossOrigin === 'string') {603        bootstrapChunks.push(604          scriptCrossOrigin,605          stringToChunk(escapeTextForBrowser(crossOrigin)),606          attributeEnd,607        );608      }609      pushCompletedShellIdAttribute(bootstrapChunks, resumableState);610      bootstrapChunks.push(endAsyncScript);611    }612  }613  if (bootstrapModules !== undefined) {614    for (let i = 0; i < bootstrapModules.length; i++) {615      const scriptConfig = bootstrapModules[i];616      let src, crossOrigin, integrity;617      const props: PreloadModuleProps = {618        rel: 'modulepreload',619        fetchPriority: 'low',620        nonce: nonceScript,621      } as any;622      if (typeof scriptConfig === 'string') {623        props.href = src = scriptConfig;624      } else {625        props.href = src = scriptConfig.src;626        props.integrity = integrity =627          typeof scriptConfig.integrity === 'string'628            ? scriptConfig.integrity629            : undefined;630        props.crossOrigin = crossOrigin =631          typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null632            ? undefined633            : scriptConfig.crossOrigin === 'use-credentials'634              ? 'use-credentials'635              : '';636      }637638      preloadBootstrapScriptOrModule(resumableState, renderState, src, props);639640      bootstrapChunks.push(641        startModuleSrc,642        stringToChunk(escapeTextForBrowser(src)),643        attributeEnd,644      );645      if (nonceScript) {646        bootstrapChunks.push(647          scriptNonce,648          stringToChunk(escapeTextForBrowser(nonceScript)),649          attributeEnd,650        );651      }652      if (typeof integrity === 'string') {653        bootstrapChunks.push(654          scriptIntegirty,655          stringToChunk(escapeTextForBrowser(integrity)),656          attributeEnd,657        );658      }659      if (typeof crossOrigin === 'string') {660        bootstrapChunks.push(661          scriptCrossOrigin,662          stringToChunk(escapeTextForBrowser(crossOrigin)),663          attributeEnd,664        );665      }666      pushCompletedShellIdAttribute(bootstrapChunks, resumableState);667      bootstrapChunks.push(endAsyncScript);668    }669  }670671  return renderState;672}673674export function resumeRenderState(675  resumableState: ResumableState,676  nonce: NonceOption | void,677): RenderState {678  return createRenderState(679    resumableState,680    nonce,681    undefined,682    undefined,683    undefined,684    undefined,685  );686}687688export function createResumableState(689  identifierPrefix: string | void,690  externalRuntimeConfig: string | BootstrapScriptDescriptor | void,691  bootstrapScriptContent: string | void,692  bootstrapScripts: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,693  bootstrapModules: $ReadOnlyArray<string | BootstrapScriptDescriptor> | void,694): ResumableState {695  const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;696697  let streamingFormat = ScriptStreamingFormat;698  if (enableFizzExternalRuntime) {699    if (externalRuntimeConfig !== undefined) {700      streamingFormat = DataStreamingFormat;701    }702  }703  return {704    idPrefix: idPrefix,705    nextFormID: 0,706    streamingFormat,707    bootstrapScriptContent,708    bootstrapScripts,709    bootstrapModules,710    instructions: NothingSent,711    hasBody: false,712    hasHtml: false,713714    // @TODO add bootstrap script to implicit preloads715716    // persistent717    unknownResources: {},718    dnsResources: {},719    connectResources: {720      default: {},721      anonymous: {},722      credentials: {},723    },724    imageResources: {},725    styleResources: {},726    scriptResources: {},727    moduleUnknownResources: {},728    moduleScriptResources: {},729  };730}731732export function resetResumableState(733  resumableState: ResumableState,734  renderState: RenderState,735): void {736  // Resets the resumable state based on what didn't manage to fully flush in the render state.737  // This currently assumes nothing was flushed.738  resumableState.nextFormID = 0;739  resumableState.hasBody = false;740  resumableState.hasHtml = false;741  resumableState.unknownResources = {742    font: renderState.resets.font,743  };744  resumableState.dnsResources = renderState.resets.dns;745  resumableState.connectResources = renderState.resets.connect;746  resumableState.imageResources = renderState.resets.image;747  resumableState.styleResources = renderState.resets.style;748  resumableState.scriptResources = {};749  resumableState.moduleUnknownResources = {};750  resumableState.moduleScriptResources = {};751  resumableState.instructions = NothingSent; // Nothing was flushed so no instructions could've flushed.752}753754export function completeResumableState(resumableState: ResumableState): void {755  // This function is called when we have completed a prerender and there is a shell.756  resumableState.bootstrapScriptContent = undefined;757  resumableState.bootstrapScripts = undefined;758  resumableState.bootstrapModules = undefined;759}760761export type PreambleState = {762  htmlChunks: null | Array<Chunk | PrecomputedChunk>,763  headChunks: null | Array<Chunk | PrecomputedChunk>,764  bodyChunks: null | Array<Chunk | PrecomputedChunk>,765};766export function createPreambleState(): PreambleState {767  return {768    htmlChunks: null,769    headChunks: null,770    bodyChunks: null,771  };772}773774// Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion775// modes. We only include the variants as they matter for the sake of our purposes.776// We don't actually provide the namespace therefore we use constants instead of the string.777export const ROOT_HTML_MODE = 0; // Used for the root most element tag.778// We have a less than HTML_HTML_MODE check elsewhere. If you add more cases here, make sure it779// still makes sense780const HTML_HTML_MODE = 1; // Used for the <html> if it is at the top level.781const HTML_MODE = 2;782const HTML_HEAD_MODE = 3;783const SVG_MODE = 4;784const MATHML_MODE = 5;785const HTML_TABLE_MODE = 6;786const HTML_TABLE_BODY_MODE = 7;787const HTML_TABLE_ROW_MODE = 8;788const HTML_COLGROUP_MODE = 9;789// We have a greater than HTML_TABLE_MODE check elsewhere. If you add more cases here, make sure it790// still makes sense791792type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;793794const NO_SCOPE = /*         */ 0b0000000;795const NOSCRIPT_SCOPE = /*   */ 0b0000001;796const PICTURE_SCOPE = /*    */ 0b0000010;797const FALLBACK_SCOPE = /*   */ 0b0000100;798const EXIT_SCOPE = /*       */ 0b0001000; // A direct Instance below a Suspense fallback is the only thing that can "exit"799const ENTER_SCOPE = /*      */ 0b0010000; // A direct Instance below Suspense content is the only thing that can "enter"800const UPDATE_SCOPE = /*     */ 0b0100000; // Inside a scope that applies "update" ViewTransitions if anything mutates here.801const APPEARING_SCOPE = /*  */ 0b1000000; // Below Suspense content subtree which might appear in an "enter" animation or "shared" animation.802const PARENT_EXIT_SCOPE = /* */ 0b10000000; // Below a ViewTransition that is exiting.803const PARENT_ENTER_SCOPE = /* */ 0b100000000; // Below a ViewTransition that is entering.804805// Everything not listed here are tracked for the whole subtree as opposed to just806// until the next Instance.807const SUBTREE_SCOPE = ~(ENTER_SCOPE | EXIT_SCOPE);808809type ViewTransitionContext = {810  update: 'none' | 'auto' | string,811  enter: 'none' | 'auto' | string,812  exit: 'none' | 'auto' | string,813  share: 'none' | 'auto' | string,814  parentEnter: 'none' | 'auto' | string,815  parentExit: 'none' | 'auto' | string,816  name: 'auto' | string,817  autoName: string, // a name that can be used if an explicit one is not defined.818  nameIdx: number, // keeps track of how many duplicates of this name we've emitted.819};820821// Lets us keep track of contextual state and pick it back up after suspending.822export type FormatContext = {823  insertionMode: InsertionMode, // root/svg/html/mathml/table824  selectedValue: null | string | Array<string>, // the selected value(s) inside a <select>, or null outside <select>825  tagScope: number,826  viewTransition: null | ViewTransitionContext, // tracks if we're inside a ViewTransition outside the first DOM node827};828829function createFormatContext(830  insertionMode: InsertionMode,831  selectedValue: null | string | Array<string>,832  tagScope: number,833  viewTransition: null | ViewTransitionContext,834): FormatContext {835  return {836    insertionMode,837    selectedValue,838    tagScope,839    viewTransition,840  };841}842843export function canHavePreamble(formatContext: FormatContext): boolean {844  return formatContext.insertionMode < HTML_MODE;845}846847export function createRootFormatContext(namespaceURI?: string): FormatContext {848  const insertionMode =849    namespaceURI === 'http://www.w3.org/2000/svg'850      ? SVG_MODE851      : namespaceURI === 'http://www.w3.org/1998/Math/MathML'852        ? MATHML_MODE853        : ROOT_HTML_MODE;854  return createFormatContext(insertionMode, null, NO_SCOPE, null);855}856857export function getChildFormatContext(858  parentContext: FormatContext,859  type: string,860  props: Object,861): FormatContext {862  const subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;863  switch (type) {864    case 'noscript':865      return createFormatContext(866        HTML_MODE,867        null,868        subtreeScope | NOSCRIPT_SCOPE,869        null,870      );871    case 'select':872      return createFormatContext(873        HTML_MODE,874        props.value != null ? props.value : props.defaultValue,875        subtreeScope,876        null,877      );878    case 'svg':879      return createFormatContext(SVG_MODE, null, subtreeScope, null);880    case 'picture':881      return createFormatContext(882        HTML_MODE,883        null,884        subtreeScope | PICTURE_SCOPE,885        null,886      );887    case 'math':888      return createFormatContext(MATHML_MODE, null, subtreeScope, null);889    case 'foreignObject':890      return createFormatContext(HTML_MODE, null, subtreeScope, null);891    // Table parents are special in that their children can only be created at all if they're892    // wrapped in a table parent. So we need to encode that we're entering this mode.893    case 'table':894      return createFormatContext(HTML_TABLE_MODE, null, subtreeScope, null);895    case 'thead':896    case 'tbody':897    case 'tfoot':898      return createFormatContext(899        HTML_TABLE_BODY_MODE,900        null,901        subtreeScope,902        null,903      );904    case 'colgroup':905      return createFormatContext(HTML_COLGROUP_MODE, null, subtreeScope, null);906    case 'tr':907      return createFormatContext(HTML_TABLE_ROW_MODE, null, subtreeScope, null);908    case 'head':909      if (parentContext.insertionMode < HTML_MODE) {910        // We are either at the root or inside the <html> tag and can enter911        // the <head> scope912        return createFormatContext(HTML_HEAD_MODE, null, subtreeScope, null);913      }914      break;915    case 'html':916      if (parentContext.insertionMode === ROOT_HTML_MODE) {917        return createFormatContext(HTML_HTML_MODE, null, subtreeScope, null);918      }919      break;920  }921  if (parentContext.insertionMode >= HTML_TABLE_MODE) {922    // Whatever tag this was, it wasn't a table parent or other special parent, so we must have923    // entered plain HTML again.924    return createFormatContext(HTML_MODE, null, subtreeScope, null);925  }926  if (parentContext.insertionMode < HTML_MODE) {927    return createFormatContext(HTML_MODE, null, subtreeScope, null);928  }929  if (enableViewTransition) {930    if (parentContext.viewTransition !== null) {931      // If we're inside a view transition, regardless what element we were in, it consumes932      // the view transition context.933      return createFormatContext(934        parentContext.insertionMode,935        parentContext.selectedValue,936        subtreeScope,937        null,938      );939    }940  }941  if (parentContext.tagScope !== subtreeScope) {942    return createFormatContext(943      parentContext.insertionMode,944      parentContext.selectedValue,945      subtreeScope,946      null,947    );948  }949  return parentContext;950}951952function getSuspenseViewTransition(953  parentViewTransition: null | ViewTransitionContext,954): null | ViewTransitionContext {955  if (parentViewTransition === null) {956    return null;957  }958  // If a ViewTransition wraps a Suspense boundary it applies to the children Instances959  // in both the fallback and the content.960  // Since we only have a representation of ViewTransitions on the Instances themselves961  // we cannot model the parent ViewTransition activating "enter", "exit" or "share"962  // since those would be ambiguous with the Suspense boundary changing states and963  // affecting the same Instances.964  // We also can't model an "update" when that update is fallback nodes swapping for965  // content nodes. However, we can model is as a "share" from the fallback nodes to966  // the content nodes using the same name. We just have to assign the same name that967  // we would've used (the parent ViewTransition name or auto-assign one).968  const viewTransition: ViewTransitionContext = {969    update: parentViewTransition.update, // For deep updates.970    enter: 'none',971    exit: 'none',972    share: parentViewTransition.update, // For exit or enter of reveals.973    parentEnter: 'none',974    parentExit: 'none',975    name: parentViewTransition.autoName,976    autoName: parentViewTransition.autoName,977    // TOOD: If we have more than just this Suspense boundary as a child of the ViewTransition978    // then the parent needs to isolate the names so that they don't conflict.979    nameIdx: 0,980  };981  return viewTransition;982}983984export function getSuspenseFallbackFormatContext(985  resumableState: ResumableState,986  parentContext: FormatContext,987): FormatContext {988  if (parentContext.tagScope & UPDATE_SCOPE) {989    // If we're rendering a Suspense in fallback mode and that is inside a ViewTransition,990    // which hasn't disabled updates, then revealing it might animate the parent so we need991    // the ViewTransition instructions.992    resumableState.instructions |= NeedUpgradeToViewTransitions;993  }994  return createFormatContext(995    parentContext.insertionMode,996    parentContext.selectedValue,997    parentContext.tagScope | FALLBACK_SCOPE | EXIT_SCOPE,998    getSuspenseViewTransition(parentContext.viewTransition),999  );1000}10011002export function getSuspenseContentFormatContext(1003  resumableState: ResumableState,1004  parentContext: FormatContext,1005): FormatContext {1006  const viewTransition = getSuspenseViewTransition(1007    parentContext.viewTransition,1008  );1009  let subtreeScope = parentContext.tagScope | ENTER_SCOPE;1010  if (viewTransition !== null && viewTransition.share !== 'none') {1011    // If we have a ViewTransition wrapping Suspense then the appearing animation1012    // will be applied just like an "enter" below. Mark it as animating.1013    subtreeScope |= APPEARING_SCOPE;1014  }1015  return createFormatContext(1016    parentContext.insertionMode,1017    parentContext.selectedValue,1018    subtreeScope,1019    viewTransition,1020  );1021}10221023export function getViewTransitionFormatContext(1024  resumableState: ResumableState,1025  parentContext: FormatContext,1026  update: ?string,1027  enter: ?string,1028  exit: ?string,1029  share: ?string,1030  parentEnter: ?string,1031  parentExit: ?string,1032  hasParentEnterHandler: boolean,1033  hasParentExitHandler: boolean,1034  name: ?string,1035  autoName: string, // name or an autogenerated unique name1036): FormatContext {1037  // We're entering a <ViewTransition>. Normalize props.1038  if (update == null) {1039    update = 'auto';1040  }1041  if (enter == null) {1042    enter = 'auto';1043  }1044  if (exit == null) {1045    exit = 'auto';1046  }1047  if (name == null) {1048    const parentViewTransition = parentContext.viewTransition;1049    if (parentViewTransition !== null) {1050      // If we have multiple nested ViewTransition and the parent has a "share"1051      // but the child doesn't, then the parent ViewTransition can still activate1052      // a share scenario so we reuse the name and share from the parent.1053      name = parentViewTransition.name;1054      share = parentViewTransition.share;1055    } else {1056      name = 'auto';1057      share = 'none'; // share is only relevant if there's an explicit name1058    }1059  } else {1060    if (share == null) {1061      share = 'auto';1062    }1063    if (parentContext.tagScope & FALLBACK_SCOPE) {1064      // If we have an explicit name and share is not disabled, and we're inside1065      // a fallback, then that fallback might pair with content and so we might need1066      // the ViewTransition instructions to animate between them.1067      resumableState.instructions |= NeedUpgradeToViewTransitions;1068    }1069  }1070  if (!(parentContext.tagScope & EXIT_SCOPE)) {1071    exit = 'none'; // exit is only relevant for the first ViewTransition inside fallback1072  } else {1073    resumableState.instructions |= NeedUpgradeToViewTransitions;1074  }1075  if (!(parentContext.tagScope & ENTER_SCOPE)) {1076    enter = 'none'; // enter is only relevant for the first ViewTransition inside content1077  } else {1078    resumableState.instructions |= NeedUpgradeToViewTransitions;1079  }1080  let resolvedParentEnter = 'none';1081  let resolvedParentExit = 'none';1082  if (enableViewTransitionParentEnterExit) {1083    if (1084      parentEnter != null &&1085      (parentContext.tagScope & PARENT_ENTER_SCOPE) !== 01086    ) {1087      resolvedParentEnter = parentEnter;1088    }1089    if (1090      parentExit != null &&1091      (parentContext.tagScope & PARENT_EXIT_SCOPE) !== 01092    ) {1093      resolvedParentExit = parentExit;1094    }1095  }1096  const viewTransition: ViewTransitionContext = {1097    update,1098    enter,1099    exit,1100    share,1101    parentEnter: resolvedParentEnter,1102    parentExit: resolvedParentExit,1103    name,1104    autoName,1105    nameIdx: 0,1106  };1107  let subtreeScope = parentContext.tagScope & SUBTREE_SCOPE;1108  if (update !== 'none') {1109    subtreeScope |= UPDATE_SCOPE;1110  } else {1111    subtreeScope &= ~UPDATE_SCOPE;1112  }1113  if (enter !== 'none') {1114    subtreeScope |= APPEARING_SCOPE;1115  }1116  if (enableViewTransitionParentEnterExit) {1117    // Parent enter relay: a ViewTransition that is itself entering starts a relay1118    // for its subtree. A nested ViewTransition continues that relay as long as it1119    // opts in with a parentEnter prop that doesn't resolve to "none", or with an1120    // onParentEnter handler. A missing parentEnter (with no handler) or an1121    // explicit "none" stops the relay so descendants below it don't participate.1122    // This mirrors commitParentEnterViewTransitions.1123    if (enter !== 'none') {1124      subtreeScope |= PARENT_ENTER_SCOPE;1125    } else if (1126      (parentContext.tagScope & PARENT_ENTER_SCOPE) !== 0 &&1127      (parentEnter === 'none' ||1128        (parentEnter === undefined && !hasParentEnterHandler))1129    ) {1130      subtreeScope &= ~PARENT_ENTER_SCOPE;1131    }1132    // Parent exit relay: mirror of the enter relay above.1133    if (exit !== 'none') {1134      subtreeScope |= PARENT_EXIT_SCOPE;1135    } else if (1136      (parentContext.tagScope & PARENT_EXIT_SCOPE) !== 0 &&1137      (parentExit === 'none' ||1138        (parentExit === undefined && !hasParentExitHandler))1139    ) {1140      subtreeScope &= ~PARENT_EXIT_SCOPE;1141    }1142  }1143  return createFormatContext(1144    parentContext.insertionMode,1145    parentContext.selectedValue,1146    subtreeScope,1147    viewTransition,1148  );1149}11501151export function isPreambleContext(formatContext: FormatContext): boolean {1152  return formatContext.insertionMode === HTML_HEAD_MODE;1153}11541155export function makeId(1156  resumableState: ResumableState,1157  treeId: string,1158  localId: number,1159): string {1160  const idPrefix = resumableState.idPrefix;11611162  let id = '_' + idPrefix + 'R_' + treeId;11631164  // Unless this is the first id at this level, append a number at the end1165  // that represents the position of this useId hook among all the useId1166  // hooks for this fiber.1167  if (localId > 0) {1168    id += 'H' + localId.toString(32);1169  }11701171  return id + '_';1172}11731174function encodeHTMLTextNode(text: string): string {1175  return escapeTextForBrowser(text);1176}11771178const textSeparator = stringToPrecomputedChunk('<!-- -->');11791180export function pushTextInstance(1181  target: Array<Chunk | PrecomputedChunk>,1182  text: string,1183  renderState: RenderState,1184  textEmbedded: boolean,1185): boolean {1186  if (text === '') {1187    // Empty text doesn't have a DOM node representation and the hydration is aware of this.1188    return textEmbedded;1189  }1190  if (textEmbedded) {1191    target.push(textSeparator);1192  }1193  target.push(stringToChunk(encodeHTMLTextNode(text)));1194  return true;1195}11961197// Called when Fizz is done with a Segment. Currently the only purpose is to conditionally1198// emit a text separator when we don't know for sure it is safe to omit1199export function pushSegmentFinale(1200  target: Array<Chunk | PrecomputedChunk>,1201  renderState: RenderState,1202  lastPushedText: boolean,1203  textEmbedded: boolean,1204): void {1205  if (lastPushedText && textEmbedded) {1206    target.push(textSeparator);1207  }1208}12091210function pushViewTransitionAttributes(1211  target: Array<Chunk | PrecomputedChunk>,1212  formatContext: FormatContext,1213): void {1214  if (!enableViewTransition) {1215    return;1216  }1217  const viewTransition = formatContext.viewTransition;1218  if (viewTransition === null) {1219    return;1220  }1221  if (viewTransition.name !== 'auto') {1222    pushStringAttribute(1223      target,1224      'vt-name',1225      viewTransition.nameIdx === 01226        ? viewTransition.name1227        : viewTransition.name + '_' + viewTransition.nameIdx,1228    );1229    // Increment the index in case we have multiple children to the same ViewTransition.1230    // Because this is a side-effect in render, we should ideally call pushViewTransitionAttributes1231    // after we've suspended (like forms do), so that we don't increment each attempt.1232    // TODO: Make this deterministic.1233    viewTransition.nameIdx++;1234  }1235  pushStringAttribute(target, 'vt-update', viewTransition.update);1236  if (viewTransition.enter !== 'none') {1237    pushStringAttribute(target, 'vt-enter', viewTransition.enter);1238  }1239  if (viewTransition.exit !== 'none') {1240    pushStringAttribute(target, 'vt-exit', viewTransition.exit);1241  }1242  if (viewTransition.share !== 'none') {1243    pushStringAttribute(target, 'vt-share', viewTransition.share);1244  }1245  if (1246    enableViewTransitionParentEnterExit &&1247    viewTransition.parentEnter !== 'none'1248  ) {1249    pushStringAttribute(target, 'vt-parent-enter', viewTransition.parentEnter);1250  }1251  if (1252    enableViewTransitionParentEnterExit &&1253    viewTransition.parentExit !== 'none'1254  ) {1255    pushStringAttribute(target, 'vt-parent-exit', viewTransition.parentExit);1256  }1257}12581259const styleNameCache: Map<string, PrecomputedChunk> = new Map();1260function processStyleName(styleName: string): PrecomputedChunk {1261  const chunk = styleNameCache.get(styleName);1262  if (chunk !== undefined) {1263    return chunk;1264  }1265  const result = stringToPrecomputedChunk(1266    escapeTextForBrowser(hyphenateStyleName(styleName)),1267  );1268  styleNameCache.set(styleName, result);1269  return result;1270}12711272const styleAttributeStart = stringToPrecomputedChunk(' style="');1273const styleAssign = stringToPrecomputedChunk(':');1274const styleSeparator = stringToPrecomputedChunk(';');12751276function pushStyleAttribute(1277  target: Array<Chunk | PrecomputedChunk>,1278  style: Object,1279): void {1280  if (typeof style !== 'object') {1281    throw new Error(1282      'The `style` prop expects a mapping from style properties to values, ' +1283        "not a string. For example, style={{marginRight: spacing + 'em'}} when " +1284        'using JSX.',1285    );1286  }12871288  let isFirst = true;1289  for (const styleName in style) {1290    if (!hasOwnProperty.call(style, styleName)) {1291      continue;1292    }1293    // If you provide unsafe user data here they can inject arbitrary CSS1294    // which may be problematic (I couldn't repro this):1295    // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet1296    // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/1297    // This is not an XSS hole but instead a potential CSS injection issue1298    // which has lead to a greater discussion about how we're going to1299    // trust URLs moving forward. See #21159011300    const styleValue = style[styleName];1301    if (1302      styleValue == null ||1303      typeof styleValue === 'boolean' ||1304      styleValue === ''1305    ) {1306      // TODO: We used to set empty string as a style with an empty value. Does that ever make sense?1307      continue;1308    }13091310    let nameChunk;1311    let valueChunk;1312    const isCustomProperty = styleName.indexOf('--') === 0;1313    if (isCustomProperty) {1314      nameChunk = stringToChunk(escapeTextForBrowser(styleName));1315      if (__DEV__) {1316        checkCSSPropertyStringCoercion(styleValue, styleName);1317      }1318      valueChunk = stringToChunk(1319        escapeTextForBrowser(('' + styleValue).trim()),1320      );1321    } else {1322      if (__DEV__) {1323        warnValidStyle(styleName, styleValue);1324      }13251326      nameChunk = processStyleName(styleName);1327      if (typeof styleValue === 'number') {1328        if (styleValue !== 0 && !isUnitlessNumber(styleName)) {1329          valueChunk = stringToChunk(styleValue + 'px'); // Presumes implicit 'px' suffix for unitless numbers1330        } else {1331          valueChunk = stringToChunk('' + styleValue);1332        }1333      } else {1334        if (__DEV__) {1335          checkCSSPropertyStringCoercion(styleValue, styleName);1336        }1337        valueChunk = stringToChunk(1338          escapeTextForBrowser(('' + styleValue).trim()),1339        );1340      }1341    }1342    if (isFirst) {1343      isFirst = false;1344      // If it's first, we don't need any separators prefixed.1345      target.push(styleAttributeStart, nameChunk, styleAssign, valueChunk);1346    } else {1347      target.push(styleSeparator, nameChunk, styleAssign, valueChunk);1348    }1349  }1350  if (!isFirst) {1351    target.push(attributeEnd);1352  }1353}13541355const attributeSeparator = stringToPrecomputedChunk(' ');1356const attributeAssign = stringToPrecomputedChunk('="');1357const attributeEnd = stringToPrecomputedChunk('"');1358const attributeEmptyString = stringToPrecomputedChunk('=""');13591360function pushBooleanAttribute(1361  target: Array<Chunk | PrecomputedChunk>,1362  name: string,1363  value: string | boolean | number | Function | Object, // not null or undefined1364): void {1365  if (value && typeof value !== 'function' && typeof value !== 'symbol') {1366    target.push(attributeSeparator, stringToChunk(name), attributeEmptyString);1367  }1368}13691370function pushStringAttribute(1371  target: Array<Chunk | PrecomputedChunk>,1372  name: string,1373  value: string | boolean | number | Function | Object, // not null or undefined1374): void {1375  if (1376    typeof value !== 'function' &&1377    typeof value !== 'symbol' &&1378    typeof value !== 'boolean'1379  ) {1380    target.push(1381      attributeSeparator,1382      stringToChunk(name),1383      attributeAssign,1384      stringToChunk(escapeTextForBrowser(value)),1385      attributeEnd,1386    );1387  }1388}13891390function makeFormFieldPrefix(resumableState: ResumableState): string {1391  // TODO: Make this deterministic.1392  const id = resumableState.nextFormID++;1393  return resumableState.idPrefix + id;1394}13951396// Since this will likely be repeated a lot in the HTML, we use a more concise message1397// than on the client and hopefully it's googleable.1398const actionJavaScriptURL = stringToPrecomputedChunk(1399  escapeTextForBrowser(1400    // eslint-disable-next-line no-script-url1401    "javascript:throw new Error('React form unexpectedly submitted.')",1402  ),1403);14041405const startHiddenInputChunk = stringToPrecomputedChunk('<input type="hidden"');14061407function pushAdditionalFormField(1408  this: Array<Chunk | PrecomputedChunk>,1409  value: string | File,1410  key: string,1411): void {1412  const target: Array<Chunk | PrecomputedChunk> = this;1413  target.push(startHiddenInputChunk);1414  validateAdditionalFormField(value, key);1415  pushStringAttribute(target, 'name', key);1416  pushStringAttribute(target, 'value', value);1417  target.push(endOfStartTagSelfClosing);1418}14191420function pushAdditionalFormFields(1421  target: Array<Chunk | PrecomputedChunk>,1422  formData: void | null | FormData,1423) {1424  if (formData != null) {1425    // $FlowFixMe[prop-missing]: FormData has forEach.1426    formData.forEach(pushAdditionalFormField, target);1427  }1428}14291430function validateAdditionalFormField(value: string | File, key: string): void {1431  if (typeof value !== 'string') {1432    throw new Error(1433      'File/Blob fields are not yet supported in progressive forms. ' +1434        'Will fallback to client hydration.',1435    );1436  }1437}14381439function validateAdditionalFormFields(formData: void | null | FormData) {1440  if (formData != null) {1441    // $FlowFixMe[prop-missing]: FormData has forEach.1442    formData.forEach(validateAdditionalFormField);1443  }1444  return formData;1445}14461447function getCustomFormFields(1448  resumableState: ResumableState,1449  formAction: any,1450): null | ReactCustomFormAction {1451  const customAction = formAction.$$FORM_ACTION;1452  if (typeof customAction === 'function') {1453    const prefix = makeFormFieldPrefix(resumableState);1454    try {1455      const customFields = formAction.$$FORM_ACTION(prefix);1456      if (customFields) {1457        validateAdditionalFormFields(customFields.data);1458      }1459      return customFields;1460    } catch (x) {1461      if (typeof x === 'object' && x !== null && typeof x.then === 'function') {1462        // Rethrow suspense.1463        throw x;1464      }1465      // If we fail to encode the form action for progressive enhancement for some reason,1466      // fallback to trying replaying on the client instead of failing the page. It might1467      // work there.1468      if (__DEV__) {1469        // TODO: Should this be some kind of recoverable error?1470        console.error(1471          'Failed to serialize an action for progressive enhancement:\n%s',1472          x,1473        );1474      }1475    }1476  }1477  return null;1478}14791480function pushFormActionAttribute(1481  target: Array<Chunk | PrecomputedChunk>,1482  resumableState: ResumableState,1483  renderState: RenderState,1484  formAction: any,1485  formEncType: any,1486  formMethod: any,1487  formTarget: any,1488  name: any,1489): void | null | FormData {1490  let formData = null;1491  if (typeof formAction === 'function') {1492    // Function form actions cannot control the form properties1493    if (__DEV__) {1494      if (name !== null && !didWarnFormActionName) {1495        didWarnFormActionName = true;1496        console.error(1497          'Cannot specify a "name" prop for a button that specifies a function as a formAction. ' +1498            'React needs it to encode which action should be invoked. It will get overridden.',1499        );1500      }1501      if (1502        (formEncType !== null || formMethod !== null) &&1503        !didWarnFormActionMethod1504      ) {1505        didWarnFormActionMethod = true;1506        console.error(1507          'Cannot specify a formEncType or formMethod for a button that specifies a ' +1508            'function as a formAction. React provides those automatically. They will get overridden.',1509        );1510      }1511      if (formTarget !== null && !didWarnFormActionTarget) {1512        didWarnFormActionTarget = true;1513        console.error(1514          'Cannot specify a formTarget for a button that specifies a function as a formAction. ' +1515            'The function will always be executed in the same window.',1516        );1517      }1518    }1519    const customFields = getCustomFormFields(resumableState, formAction);1520    if (customFields !== null) {1521      // This action has a custom progressive enhancement form that can submit the form1522      // back to the server if it's invoked before hydration. Such as a Server Action.1523      name = customFields.name;1524      formAction = customFields.action || '';1525      formEncType = customFields.encType;1526      formMethod = customFields.method;1527      formTarget = customFields.target;1528      formData = customFields.data;1529    } else {1530      // Set a javascript URL that doesn't do anything. We don't expect this to be invoked1531      // because we'll preventDefault in the Fizz runtime, but it can happen if a form is1532      // manually submitted or if someone calls stopPropagation before React gets the event.1533      // If CSP is used to block javascript: URLs that's fine too. It just won't show this1534      // error message but the URL will be logged.1535      target.push(1536        attributeSeparator,1537        stringToChunk('formAction'),1538        attributeAssign,1539        actionJavaScriptURL,1540        attributeEnd,1541      );1542      name = null;1543      formAction = null;1544      formEncType = null;1545      formMethod = null;1546      formTarget = null;1547      injectFormReplayingRuntime(resumableState, renderState);1548    }1549  }1550  if (name != null) {1551    pushAttribute(target, 'name', name);1552  }1553  if (formAction != null) {1554    pushAttribute(target, 'formAction', formAction);1555  }1556  if (formEncType != null) {1557    pushAttribute(target, 'formEncType', formEncType);1558  }1559  if (formMethod != null) {1560    pushAttribute(target, 'formMethod', formMethod);1561  }1562  if (formTarget != null) {1563    pushAttribute(target, 'formTarget', formTarget);1564  }1565  return formData;1566}15671568let blobCache: null | WeakMap<Blob, Thenable<string>> = null;15691570function pushSrcObjectAttribute(1571  target: Array<Chunk | PrecomputedChunk>,1572  blob: Blob,1573): void {1574  // Throwing a Promise style suspense read of the Blob content.1575  if (blobCache === null) {1576    blobCache = new WeakMap();1577  }1578  const suspenseCache: WeakMap<Blob, Thenable<string>> = blobCache;1579  let thenable = suspenseCache.get(blob);1580  if (thenable === undefined) {1581    thenable = readAsDataURL(blob) as any as Thenable<string>;1582    thenable.then(1583      result => {1584        (thenable as any).status = 'fulfilled';1585        (thenable as any).value = result;1586      },1587      error => {1588        (thenable as any).status = 'rejected';1589        (thenable as any).reason = error;1590      },1591    );1592    suspenseCache.set(blob, thenable);1593  }1594  if (thenable.status === 'rejected') {1595    throw thenable.reason;1596  } else if (thenable.status !== 'fulfilled') {1597    throw thenable;1598  }1599  const url = thenable.value;1600  target.push(1601    attributeSeparator,1602    stringToChunk('src'),1603    attributeAssign,1604    stringToChunk(escapeTextForBrowser(url)),1605    attributeEnd,1606  );1607}16081609function pushAttribute(1610  target: Array<Chunk | PrecomputedChunk>,1611  name: string,1612  value: string | boolean | number | Function | Object, // not null or undefined1613): void {1614  switch (name) {1615    // These are very common props and therefore are in the beginning of the switch.1616    // TODO: aria-label is a very common prop but allows booleans so is not like the others1617    // but should ideally go in this list too.1618    case 'className': {1619      pushStringAttribute(target, 'class', value);1620      break;1621    }1622    case 'tabIndex': {1623      pushStringAttribute(target, 'tabindex', value);1624      break;1625    }1626    case 'dir':1627    case 'role':1628    case 'viewBox':1629    case 'width':1630    case 'height': {1631      pushStringAttribute(target, name, value);1632      break;1633    }1634    case 'style': {1635      pushStyleAttribute(target, value);1636      return;1637    }1638    case 'src': {1639      // $FlowFixMe[invalid-compare]1640      if (enableSrcObject && typeof value === 'object' && value !== null) {1641        if (typeof Blob === 'function' && value instanceof Blob) {1642          pushSrcObjectAttribute(target, value);1643          return;1644        }1645      }1646      // Fallthrough to general urls1647    }1648    case 'href': {1649      if (value === '') {1650        if (__DEV__) {1651          if (name === 'src') {1652            console.error(1653              'An empty string ("") was passed to the %s attribute. ' +1654                'This may cause the browser to download the whole page again over the network. ' +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          } else {1661            console.error(1662              'An empty string ("") was passed to the %s attribute. ' +1663                'To fix this, either do not render the element at all ' +1664                'or pass null to %s instead of an empty string.',1665              name,1666              name,1667            );1668          }1669        }1670        return;1671      }1672    }1673    // Fall through to the last case which shouldn't remove empty strings.1674    case 'action':1675    case 'formAction': {1676      // TODO: Consider only special casing these for each tag.1677      if (1678        value == null ||1679        typeof value === 'function' ||1680        typeof value === 'symbol' ||1681        typeof value === 'boolean'1682      ) {1683        return;1684      }1685      if (__DEV__) {1686        checkAttributeStringCoercion(value, name);1687      }1688      const sanitizedValue = sanitizeURL('' + value);1689      target.push(1690        attributeSeparator,1691        stringToChunk(name),1692        attributeAssign,1693        stringToChunk(escapeTextForBrowser(sanitizedValue)),1694        attributeEnd,1695      );1696      return;1697    }1698    case 'defaultValue':1699    case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements.1700    case 'innerHTML': // Must use dangerouslySetInnerHTML instead.1701    case 'suppressContentEditableWarning':1702    case 'suppressHydrationWarning':1703    case 'ref':1704      // Ignored. These are built-in to React on the client.1705      return;1706    case 'autoFocus':1707    case 'multiple':1708    case 'muted': {1709      pushBooleanAttribute(target, name.toLowerCase(), value);1710      return;1711    }1712    case 'xlinkHref': {1713      if (1714        typeof value === 'function' ||1715        typeof value === 'symbol' ||1716        typeof value === 'boolean'1717      ) {1718        return;1719      }1720      if (__DEV__) {1721        checkAttributeStringCoercion(value, name);1722      }1723      const sanitizedValue = sanitizeURL('' + value);1724      target.push(1725        attributeSeparator,1726        stringToChunk('xlink:href'),1727        attributeAssign,1728        stringToChunk(escapeTextForBrowser(sanitizedValue)),1729        attributeEnd,1730      );1731      return;1732    }1733    case 'contentEditable':1734    case 'spellCheck':1735    case 'draggable':1736    case 'value':1737    case 'autoReverse':1738    case 'externalResourcesRequired':1739    case 'focusable':1740    case 'preserveAlpha': {1741      // Booleanish String1742      // These are "enumerated" attributes that accept "true" and "false".1743      // In React, we let users pass `true` and `false` even though technically1744      // these aren't boolean attributes (they are coerced to strings).1745      if (typeof value !== 'function' && typeof value !== 'symbol') {1746        target.push(1747          attributeSeparator,1748          stringToChunk(name),1749          attributeAssign,1750          stringToChunk(escapeTextForBrowser(value)),1751          attributeEnd,1752        );1753      }1754      return;1755    }1756    case 'inert': {1757      if (__DEV__) {1758        if (value === '' && !didWarnForNewBooleanPropsWithEmptyValue[name]) {1759          didWarnForNewBooleanPropsWithEmptyValue[name] = true;1760          console.error(1761            'Received an empty string for a boolean attribute `%s`. ' +1762              'This will treat the attribute as if it were false. ' +1763              'Either pass `false` to silence this warning, or ' +1764              'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',1765            name,1766          );1767        }1768      }1769    }1770    // Fallthrough for boolean props that don't have a warning for empty strings.1771    case 'allowFullScreen':1772    case 'async':1773    case 'autoPlay':1774    case 'controls':1775    case 'credentialless':1776    case 'default':1777    case 'defer':1778    case 'disabled':1779    case 'disablePictureInPicture':1780    case 'disableRemotePlayback':1781    case 'formNoValidate':1782    case 'hidden':1783    case 'loop':1784    case 'noModule':1785    case 'noValidate':1786    case 'open':1787    case 'playsInline':1788    case 'readOnly':1789    case 'required':1790    case 'reversed':1791    case 'scoped':1792    case 'seamless':1793    case 'itemScope': {1794      // Boolean1795      if (value && typeof value !== 'function' && typeof value !== 'symbol') {1796        target.push(1797          attributeSeparator,1798          stringToChunk(name),1799          attributeEmptyString,1800        );1801      }1802      return;1803    }1804    case 'capture':1805    case 'download': {1806      // Overloaded Boolean1807      if (value === true) {1808        target.push(1809          attributeSeparator,1810          stringToChunk(name),1811          attributeEmptyString,1812        );1813      } else if (value === false) {1814        // Ignored1815      } else if (typeof value !== 'function' && typeof value !== 'symbol') {1816        target.push(1817          attributeSeparator,1818          stringToChunk(name),1819          attributeAssign,1820          stringToChunk(escapeTextForBrowser(value)),1821          attributeEnd,1822        );1823      }1824      return;1825    }1826    case 'cols':1827    case 'rows':1828    case 'size':1829    case 'span': {1830      // These are HTML attributes that must be positive numbers.1831      if (1832        typeof value !== 'function' &&1833        typeof value !== 'symbol' &&1834        !isNaN(value) &&1835        (value as any) >= 11836      ) {1837        target.push(1838          attributeSeparator,1839          stringToChunk(name),1840          attributeAssign,1841          stringToChunk(escapeTextForBrowser(value)),1842          attributeEnd,1843        );1844      }1845      return;1846    }1847    case 'rowSpan':1848    case 'start': {1849      // These are HTML attributes that must be numbers.1850      if (1851        typeof value !== 'function' &&1852        typeof value !== 'symbol' &&1853        !isNaN(value)1854      ) {1855        target.push(1856          attributeSeparator,1857          stringToChunk(name),1858          attributeAssign,1859          stringToChunk(escapeTextForBrowser(value)),1860          attributeEnd,1861        );1862      }1863      return;1864    }1865    case 'xlinkActuate':1866      pushStringAttribute(target, 'xlink:actuate', value);1867      return;1868    case 'xlinkArcrole':1869      pushStringAttribute(target, 'xlink:arcrole', value);1870      return;1871    case 'xlinkRole':1872      pushStringAttribute(target, 'xlink:role', value);1873      return;1874    case 'xlinkShow':1875      pushStringAttribute(target, 'xlink:show', value);1876      return;1877    case 'xlinkTitle':1878      pushStringAttribute(target, 'xlink:title', value);1879      return;1880    case 'xlinkType':1881      pushStringAttribute(target, 'xlink:type', value);1882      return;1883    case 'xmlBase':1884      pushStringAttribute(target, 'xml:base', value);1885      return;1886    case 'xmlLang':1887      pushStringAttribute(target, 'xml:lang', value);1888      return;1889    case 'xmlSpace':1890      pushStringAttribute(target, 'xml:space', value);1891      return;1892    default:1893      if (1894        // shouldIgnoreAttribute1895        // We have already filtered out null/undefined and reserved words.1896        name.length > 2 &&1897        (name[0] === 'o' || name[0] === 'O') &&1898        (name[1] === 'n' || name[1] === 'N')1899      ) {1900        return;1901      }19021903      const attributeName = getAttributeAlias(name);1904      if (isAttributeNameSafe(attributeName)) {1905        // shouldRemoveAttribute1906        switch (typeof value) {1907          case 'function':1908          case 'symbol':1909            return;1910          case 'boolean': {1911            const prefix = attributeName.toLowerCase().slice(0, 5);1912            if (prefix !== 'data-' && prefix !== 'aria-') {1913              return;1914            }1915          }1916        }1917        target.push(1918          attributeSeparator,1919          stringToChunk(attributeName),1920          attributeAssign,1921          stringToChunk(escapeTextForBrowser(value)),1922          attributeEnd,1923        );1924      }1925  }1926}19271928const endOfStartTag = stringToPrecomputedChunk('>');1929const endOfStartTagSelfClosing = stringToPrecomputedChunk('/>');19301931function pushInnerHTML(1932  target: Array<Chunk | PrecomputedChunk>,1933  innerHTML: any,1934  children: any,1935) {1936  if (innerHTML != null) {1937    if (children != null) {1938      throw new Error(1939        'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',1940      );1941    }19421943    if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) {1944      throw new Error(1945        '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +1946          'Please visit https://react.dev/link/dangerously-set-inner-html ' +1947          'for more information.',1948      );1949    }19501951    const html = innerHTML.__html;1952    if (html !== null && html !== undefined) {1953      if (__DEV__) {1954        checkHtmlStringCoercion(html);1955      }1956      target.push(stringToChunk('' + html));1957    }1958  }1959}19601961// TODO: Move these to RenderState so that we warn for every request.1962// It would help debugging in stateful servers (e.g. service worker).1963let didWarnDefaultInputValue = false;1964let didWarnDefaultChecked = false;1965let didWarnDefaultSelectValue = false;1966let didWarnDefaultTextareaValue = false;1967let didWarnInvalidOptionChildren = false;1968let didWarnInvalidOptionInnerHTML = false;1969let didWarnSelectedSetOnOption = false;1970let didWarnFormActionType = false;1971let didWarnFormActionName = false;1972let didWarnFormActionTarget = false;1973let didWarnFormActionMethod = false;19741975function checkSelectProp(props: any, propName: string) {1976  if (__DEV__) {1977    const value = props[propName];1978    if (value != null) {1979      const array = isArray(value);1980      if (props.multiple && !array) {1981        console.error(1982          'The `%s` prop supplied to <select> must be an array if ' +1983            '`multiple` is true.',1984          propName,1985        );1986      } else if (!props.multiple && array) {1987        console.error(1988          'The `%s` prop supplied to <select> must be a scalar ' +1989            'value if `multiple` is false.',1990          propName,1991        );1992      }1993    }1994  }1995}19961997function pushStartAnchor(1998  target: Array<Chunk | PrecomputedChunk>,1999  props: Object,2000  formatContext: FormatContext,

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.

Generated by searchcode.com · powered by code_get_file + code_get_findings