diff options
author | 2022-11-03 18:10:42 -0700 | |
---|---|---|
committer | 2022-11-03 18:10:42 -0700 | |
commit | d04c0d51fe609c569efa8139d66437d60dc2b4e6 (patch) | |
tree | 7e66e2eeebff3f36fde6232a0758f021082b26ec /test/bun.js/react-dom-server.bun.js | |
parent | a5b25c5a7941f792db477d110157f13dbfe99c28 (diff) | |
download | bun-d04c0d51fe609c569efa8139d66437d60dc2b4e6.tar.gz bun-d04c0d51fe609c569efa8139d66437d60dc2b4e6.tar.zst bun-d04c0d51fe609c569efa8139d66437d60dc2b4e6.zip |
Add minified prod build of react-dom/server.bun (#1458)
Co-authored-by: Colin McDonnell <colinmcd@alum.mit.edu>
Diffstat (limited to '')
-rw-r--r-- | test/bun.js/react-dom-server.bun.js | 6819 |
1 files changed, 0 insertions, 6819 deletions
diff --git a/test/bun.js/react-dom-server.bun.js b/test/bun.js/react-dom-server.bun.js deleted file mode 100644 index e5e03dcd2..000000000 --- a/test/bun.js/react-dom-server.bun.js +++ /dev/null @@ -1,6819 +0,0 @@ -/** - * @license React - * react-dom-server.bun.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - Children, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED$1, -} from 'react'; -import {__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from 'react-dom'; - -// TODO: this is special because it gets imported during build. -// -// TODO: 18.0.0 has not been released to NPM; -// It exists as a placeholder so that DevTools can support work tag changes between releases. -// When we next publish a release, update the matching TODO in backend/renderer.js -// TODO: This module is used both by the release scripts and to expose a version -// at runtime. We should instead inject the version number as part of the build -// process, and use the ReactVersions.js module as the single source of truth. -var ReactVersion = '18.2.0'; - -function scheduleWork(callback) { - callback(); -} -function beginWriting(destination) {} -function writeChunk(destination, chunk) { - if (chunk.length === 0) { - return; - } - - destination.write(chunk); -} -function writeChunkAndReturn(destination, chunk) { - return !!destination.write(chunk); -} -function completeWriting(destination) {} -function close(destination) { - destination.end(); -} -function stringToChunk(content) { - return content; -} -function stringToPrecomputedChunk(content) { - return content; -} -function closeWithError(destination, error) { - // $FlowFixMe[method-unbinding] - if (typeof destination.error === 'function') { - // $FlowFixMe: This is an Error object or the destination accepts other types. - destination.error(error); - } else { - // Earlier implementations doesn't support this method. In that environment you're - // supposed to throw from a promise returned but we don't return a promise in our - // approach. We could fork this implementation but this is environment is an edge - // case to begin with. It's even less common to run this in an older environment. - // Even then, this is not where errors are supposed to happen and they get reported - // to a global callback in addition to this anyway. So it's fine just to close this. - destination.close(); - } -} - -// ----------------------------------------------------------------------------- -const enableFloat = true; // When a node is unmounted, recurse into the Fiber subtree and clean out - -// $FlowFixMe[method-unbinding] -const hasOwnProperty = Object.prototype.hasOwnProperty; - -// A reserved attribute. -// It is handled by React separately and shouldn't be written to the DOM. -const RESERVED = 0; // A simple string attribute. -// Attributes that aren't in the filter are presumed to have this type. - -const STRING = 1; // A string attribute that accepts booleans in React. In HTML, these are called -// "enumerated" attributes with "true" and "false" as possible values. -// When true, it should be set to a "true" string. -// When false, it should be set to a "false" string. - -const BOOLEANISH_STRING = 2; // A real boolean attribute. -// When true, it should be present (set either to an empty string or its name). -// When false, it should be omitted. - -const BOOLEAN = 3; // An attribute that can be used as a flag as well as with a value. -// When true, it should be present (set either to an empty string or its name). -// When false, it should be omitted. -// For any other value, should be present with that value. - -const OVERLOADED_BOOLEAN = 4; // An attribute that must be numeric or parse as a numeric. -// When falsy, it should be removed. - -const NUMERIC = 5; // An attribute that must be positive numeric or parse as a positive numeric. -// When falsy, it should be removed. - -const POSITIVE_NUMERIC = 6; - -/* eslint-disable max-len */ -const ATTRIBUTE_NAME_START_CHAR = - ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD'; -/* eslint-enable max-len */ - -const ATTRIBUTE_NAME_CHAR = - ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040'; -const VALID_ATTRIBUTE_NAME_REGEX = new RegExp( - '^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$' -); -const illegalAttributeNameCache = {}; -const validatedAttributeNameCache = {}; -function isAttributeNameSafe(attributeName) { - if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) { - return true; - } - - if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) { - return false; - } - - if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { - validatedAttributeNameCache[attributeName] = true; - return true; - } - - illegalAttributeNameCache[attributeName] = true; - - return false; -} -function getPropertyInfo(name) { - return properties.hasOwnProperty(name) ? properties[name] : null; -} - -function PropertyInfoRecord( - name, - type, - mustUseProperty, - attributeName, - attributeNamespace, - sanitizeURL, - removeEmptyString -) { - this.acceptsBooleans = - type === BOOLEANISH_STRING || - type === BOOLEAN || - type === OVERLOADED_BOOLEAN; - this.attributeName = attributeName; - this.attributeNamespace = attributeNamespace; - this.mustUseProperty = mustUseProperty; - this.propertyName = name; - this.type = type; - this.sanitizeURL = sanitizeURL; - this.removeEmptyString = removeEmptyString; -} // When adding attributes to this list, be sure to also add them to -// the `possibleStandardNames` module to ensure casing and incorrect -// name warnings. - -const properties = {}; // These props are reserved by React. They shouldn't be written to the DOM. - -const reservedProps = [ - 'children', - 'dangerouslySetInnerHTML', // TODO: This prevents the assignment of defaultValue to regular - // elements (not just inputs). Now that ReactDOMInput assigns to the - // defaultValue property -- do we need this? - 'defaultValue', - 'defaultChecked', - 'innerHTML', - 'suppressContentEditableWarning', - 'suppressHydrationWarning', - 'style', -]; - -{ - reservedProps.push('innerText', 'textContent'); -} - -reservedProps.forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - RESERVED, - false, // mustUseProperty - name, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // A few React string attributes have a different name. -// This is a mapping from React prop names to the attribute names. - -[ - ['acceptCharset', 'accept-charset'], - ['className', 'class'], - ['htmlFor', 'for'], - ['httpEquiv', 'http-equiv'], -].forEach((_ref) => { - let name = _ref[0], - attributeName = _ref[1]; - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - STRING, - false, // mustUseProperty - attributeName, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are "enumerated" HTML attributes that accept "true" and "false". -// In React, we let users pass `true` and `false` even though technically -// these aren't boolean attributes (they are coerced to strings). - -['contentEditable', 'draggable', 'spellCheck', 'value'].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - BOOLEANISH_STRING, - false, // mustUseProperty - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are "enumerated" SVG attributes that accept "true" and "false". -// In React, we let users pass `true` and `false` even though technically -// these aren't boolean attributes (they are coerced to strings). -// Since these are SVG attributes, their attribute names are case-sensitive. - -[ - 'autoReverse', - 'externalResourcesRequired', - 'focusable', - 'preserveAlpha', -].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - BOOLEANISH_STRING, - false, // mustUseProperty - name, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are HTML boolean attributes. - -[ - 'allowFullScreen', - 'async', // Note: there is a special case that prevents it from being written to the DOM - // on the client side because the browsers are inconsistent. Instead we call focus(). - 'autoFocus', - 'autoPlay', - 'controls', - 'default', - 'defer', - 'disabled', - 'disablePictureInPicture', - 'disableRemotePlayback', - 'formNoValidate', - 'hidden', - 'loop', - 'noModule', - 'noValidate', - 'open', - 'playsInline', - 'readOnly', - 'required', - 'reversed', - 'scoped', - 'seamless', // Microdata - 'itemScope', -].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - BOOLEAN, - false, // mustUseProperty - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are the few React props that we set as DOM properties -// rather than attributes. These are all booleans. - -[ - 'checked', // Note: `option.selected` is not updated if `select.multiple` is - // disabled with `removeAttribute`. We have special logic for handling this. - 'multiple', - 'muted', - 'selected', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - BOOLEAN, - true, // mustUseProperty - name, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are HTML attributes that are "overloaded booleans": they behave like -// booleans, but can also accept a string value. - -[ - 'capture', - 'download', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - OVERLOADED_BOOLEAN, - false, // mustUseProperty - name, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are HTML attributes that must be positive numbers. - -[ - 'cols', - 'rows', - 'size', - 'span', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - POSITIVE_NUMERIC, - false, // mustUseProperty - name, // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These are HTML attributes that must be numbers. - -['rowSpan', 'start'].forEach((name) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[name] = new PropertyInfoRecord( - name, - NUMERIC, - false, // mustUseProperty - name.toLowerCase(), // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); -const CAMELIZE = /[\-\:]([a-z])/g; - -const capitalize = (token) => token[1].toUpperCase(); // This is a list of all SVG attributes that need special casing, namespacing, -// or boolean value assignment. Regular attributes that just accept strings -// and have the same names are omitted, just like in the HTML attribute filter. -// Some of these attributes can be hard to find. This list was created by -// scraping the MDN documentation. - -[ - 'accent-height', - 'alignment-baseline', - 'arabic-form', - 'baseline-shift', - 'cap-height', - 'clip-path', - 'clip-rule', - 'color-interpolation', - 'color-interpolation-filters', - 'color-profile', - 'color-rendering', - 'dominant-baseline', - 'enable-background', - 'fill-opacity', - 'fill-rule', - 'flood-color', - 'flood-opacity', - 'font-family', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-weight', - 'glyph-name', - 'glyph-orientation-horizontal', - 'glyph-orientation-vertical', - 'horiz-adv-x', - 'horiz-origin-x', - 'image-rendering', - 'letter-spacing', - 'lighting-color', - 'marker-end', - 'marker-mid', - 'marker-start', - 'overline-position', - 'overline-thickness', - 'paint-order', - 'panose-1', - 'pointer-events', - 'rendering-intent', - 'shape-rendering', - 'stop-color', - 'stop-opacity', - 'strikethrough-position', - 'strikethrough-thickness', - 'stroke-dasharray', - 'stroke-dashoffset', - 'stroke-linecap', - 'stroke-linejoin', - 'stroke-miterlimit', - 'stroke-opacity', - 'stroke-width', - 'text-anchor', - 'text-decoration', - 'text-rendering', - 'underline-position', - 'underline-thickness', - 'unicode-bidi', - 'unicode-range', - 'units-per-em', - 'v-alphabetic', - 'v-hanging', - 'v-ideographic', - 'v-mathematical', - 'vector-effect', - 'vert-adv-y', - 'vert-origin-x', - 'vert-origin-y', - 'word-spacing', - 'writing-mode', - 'xmlns:xlink', - 'x-height', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((attributeName) => { - const name = attributeName.replace(CAMELIZE, capitalize); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - - properties[name] = new PropertyInfoRecord( - name, - STRING, - false, // mustUseProperty - attributeName, - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // String SVG attributes with the xlink namespace. - -[ - 'xlink:actuate', - 'xlink:arcrole', - 'xlink:role', - 'xlink:show', - 'xlink:title', - 'xlink:type', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((attributeName) => { - const name = attributeName.replace(CAMELIZE, capitalize); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - - properties[name] = new PropertyInfoRecord( - name, - STRING, - false, // mustUseProperty - attributeName, - 'http://www.w3.org/1999/xlink', - false, // sanitizeURL - false - ); -}); // String SVG attributes with the xml namespace. - -[ - 'xml:base', - 'xml:lang', - 'xml:space', // NOTE: if you add a camelCased prop to this list, - // you'll need to set attributeName to name.toLowerCase() - // instead in the assignment below. -].forEach((attributeName) => { - const name = attributeName.replace(CAMELIZE, capitalize); // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - - properties[name] = new PropertyInfoRecord( - name, - STRING, - false, // mustUseProperty - attributeName, - 'http://www.w3.org/XML/1998/namespace', - false, // sanitizeURL - false - ); -}); // These attribute exists both in HTML and SVG. -// The attribute name is case-sensitive in SVG so we can't just use -// the React name like we do for attributes that exist only in HTML. - -['tabIndex', 'crossOrigin'].forEach((attributeName) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[attributeName] = new PropertyInfoRecord( - attributeName, - STRING, - false, // mustUseProperty - attributeName.toLowerCase(), // attributeName - null, // attributeNamespace - false, // sanitizeURL - false - ); -}); // These attributes accept URLs. These must not allow javascript: URLS. -// These will also need to accept Trusted Types object in the future. - -const xlinkHref = 'xlinkHref'; // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - -properties[xlinkHref] = new PropertyInfoRecord( - 'xlinkHref', - STRING, - false, // mustUseProperty - 'xlink:href', - 'http://www.w3.org/1999/xlink', - true, // sanitizeURL - false -); -['src', 'href', 'action', 'formAction'].forEach((attributeName) => { - // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions - properties[attributeName] = new PropertyInfoRecord( - attributeName, - STRING, - false, // mustUseProperty - attributeName.toLowerCase(), // attributeName - null, // attributeNamespace - true, // sanitizeURL - true - ); -}); - -/** - * CSS properties which accept numbers but are not in units of "px". - */ -const isUnitlessNumber = { - animationIterationCount: true, - aspectRatio: true, - borderImageOutset: true, - borderImageSlice: true, - borderImageWidth: true, - boxFlex: true, - boxFlexGroup: true, - boxOrdinalGroup: true, - columnCount: true, - columns: true, - flex: true, - flexGrow: true, - flexPositive: true, - flexShrink: true, - flexNegative: true, - flexOrder: true, - gridArea: true, - gridRow: true, - gridRowEnd: true, - gridRowSpan: true, - gridRowStart: true, - gridColumn: true, - gridColumnEnd: true, - gridColumnSpan: true, - gridColumnStart: true, - fontWeight: true, - lineClamp: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - tabSize: true, - widows: true, - zIndex: true, - zoom: true, - // SVG-related properties - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeDasharray: true, - strokeDashoffset: true, - strokeMiterlimit: true, - strokeOpacity: true, - strokeWidth: true, -}; -/** - * @param {string} prefix vendor-specific prefix, eg: Webkit - * @param {string} key style name, eg: transitionDuration - * @return {string} style name prefixed with `prefix`, properly camelCased, eg: - * WebkitTransitionDuration - */ - -function prefixKey(prefix, key) { - return prefix + key.charAt(0).toUpperCase() + key.substring(1); -} -/** - * Support style names that may come passed in prefixed by adding permutations - * of vendor prefixes. - */ - -const prefixes = ['Webkit', 'ms', 'Moz', 'O']; // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an -// infinite loop, because it iterates over the newly added props too. - -Object.keys(isUnitlessNumber).forEach(function (prop) { - prefixes.forEach(function (prefix) { - isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; - }); -}); - -// code copied and modified from escape-html -const matchHtmlRegExp = /["'&<>]/; -/** - * Escapes special characters and HTML entities in a given html string. - * - * @param {string} string HTML string to escape for later insertion - * @return {string} - * @public - */ - -function escapeHtml(string) { - const str = '' + string; - const match = matchHtmlRegExp.exec(str); - - if (!match) { - return str; - } - - let escape; - let html = ''; - let index; - let lastIndex = 0; - - for (index = match.index; index < str.length; index++) { - switch (str.charCodeAt(index)) { - case 34: - // " - escape = '"'; - break; - - case 38: - // & - escape = '&'; - break; - - case 39: - // ' - escape = '''; // modified from escape-html; used to be ''' - - break; - - case 60: - // < - escape = '<'; - break; - - case 62: - // > - escape = '>'; - break; - - default: - continue; - } - - if (lastIndex !== index) { - html += str.substring(lastIndex, index); - } - - lastIndex = index + 1; - html += escape; - } - - return lastIndex !== index ? html + str.substring(lastIndex, index) : html; -} // end code copied and modified from escape-html - -/** - * Escapes text to prevent scripting attacks. - * - * @param {*} text Text value to escape. - * @return {string} An escaped string. - */ - -function escapeTextForBrowser(text) { - if (typeof text === 'boolean' || typeof text === 'number') { - // this shortcircuit helps perf for types that we know will never have - // special characters, especially given that this function is used often - // for numeric dom ids. - return '' + text; - } - - return escapeHtml(text); -} - -const uppercasePattern = /([A-Z])/g; -const msPattern = /^ms-/; -/** - * Hyphenates a camelcased CSS property name, for example: - * - * > hyphenateStyleName('backgroundColor') - * < "background-color" - * > hyphenateStyleName('MozTransition') - * < "-moz-transition" - * > hyphenateStyleName('msTransition') - * < "-ms-transition" - * - * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix - * is converted to `-ms-`. - */ - -function hyphenateStyleName(name) { - return name - .replace(uppercasePattern, '-$1') - .toLowerCase() - .replace(msPattern, '-ms-'); -} - -const isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare - -function isArray(a) { - return isArrayImpl(a); -} - -const assign = Object.assign; - -// @TODO add bootstrap script to implicit preloads -function createResources() { - return { - // persistent - preloadsMap: new Map(), - stylesMap: new Map(), - scriptsMap: new Map(), - headsMap: new Map(), - // cleared on flush - charset: null, - bases: new Set(), - preconnects: new Set(), - fontPreloads: new Set(), - // usedImagePreloads: new Set(), - precedences: new Map(), - usedStylePreloads: new Set(), - scripts: new Set(), - usedScriptPreloads: new Set(), - explicitStylePreloads: new Set(), - // explicitImagePreloads: new Set(), - explicitScriptPreloads: new Set(), - headResources: new Set(), - // cache for tracking structured meta tags - structuredMetaKeys: new Map(), - // like a module global for currently rendering boundary - boundaryResources: null, - }; -} -function createBoundaryResources() { - return new Set(); -} -let currentResources = null; -const currentResourcesStack = []; -function prepareToRenderResources(resources) { - currentResourcesStack.push(currentResources); - currentResources = resources; -} -function finishRenderingResources() { - currentResources = currentResourcesStack.pop(); -} -function setCurrentlyRenderingBoundaryResourcesTarget( - resources, - boundaryResources -) { - resources.boundaryResources = boundaryResources; -} -const ReactDOMServerDispatcher = { - preload, - preinit, -}; - -function preload(href, options) { - if (!currentResources) { - // While we expect that preload calls are primarily going to be observed - // during render because effects and events don't run on the server it is - // still possible that these get called in module scope. This is valid on - // the client since there is still a document to interact with but on the - // server we need a request to associate the call to. Because of this we - // simply return and do not warn. - return; - } - - const resources = currentResources; - - if ( - typeof href === 'string' && - href && - typeof options === 'object' && - options !== null - ) { - const as = options.as; - let resource = resources.preloadsMap.get(href); - - if (resource); - else { - resource = createPreloadResource( - resources, - href, - as, - preloadPropsFromPreloadOptions(href, as, options) - ); - } - - switch (as) { - case 'font': { - resources.fontPreloads.add(resource); - break; - } - - case 'style': { - resources.explicitStylePreloads.add(resource); - break; - } - - case 'script': { - resources.explicitScriptPreloads.add(resource); - break; - } - } - } -} - -function preinit(href, options) { - if (!currentResources) { - // While we expect that preinit calls are primarily going to be observed - // during render because effects and events don't run on the server it is - // still possible that these get called in module scope. This is valid on - // the client since there is still a document to interact with but on the - // server we need a request to associate the call to. Because of this we - // simply return and do not warn. - return; - } - - const resources = currentResources; - - if ( - typeof href === 'string' && - href && - typeof options === 'object' && - options !== null - ) { - const as = options.as; - - switch (as) { - case 'style': { - let resource = resources.stylesMap.get(href); - - if (resource); - else { - const precedence = options.precedence || 'default'; - const resourceProps = stylePropsFromPreinitOptions( - href, - precedence, - options - ); - resource = createStyleResource( - resources, - href, - precedence, - resourceProps - ); - } - - resource.set.add(resource); - resources.explicitStylePreloads.add(resource.hint); - return; - } - - case 'script': { - const src = href; - let resource = resources.scriptsMap.get(src); - - if (resource); - else { - const scriptProps = scriptPropsFromPreinitOptions(src, options); - resource = createScriptResource(resources, src, scriptProps); - resources.scripts.add(resource); - } - - return; - } - } - } -} - -function preloadPropsFromPreloadOptions(href, as, options) { - return { - href, - rel: 'preload', - as, - crossOrigin: as === 'font' ? '' : options.crossOrigin, - integrity: options.integrity, - }; -} - -function preloadPropsFromRawProps(href, as, rawProps) { - const props = assign({}, rawProps); - - props.href = href; - props.rel = 'preload'; - props.as = as; - - if (as === 'font') { - // Font preloads always need CORS anonymous mode so we set it here - // regardless of the props provided. This should warn elsewhere in - // dev - props.crossOrigin = ''; - } - - return props; -} - -function preloadAsStylePropsFromProps(href, props) { - return { - rel: 'preload', - as: 'style', - href: href, - crossOrigin: props.crossOrigin, - integrity: props.integrity, - media: props.media, - hrefLang: props.hrefLang, - referrerPolicy: props.referrerPolicy, - }; -} - -function preloadAsScriptPropsFromProps(href, props) { - return { - rel: 'preload', - as: 'script', - href, - crossOrigin: props.crossOrigin, - integrity: props.integrity, - referrerPolicy: props.referrerPolicy, - }; -} - -function createPreloadResource(resources, href, as, props) { - const preloadsMap = resources.preloadsMap; - - const resource = { - type: 'preload', - as, - href, - flushed: false, - props, - }; - preloadsMap.set(href, resource); - return resource; -} - -function stylePropsFromRawProps(href, precedence, rawProps) { - const props = assign({}, rawProps); - - props.href = href; - props.rel = 'stylesheet'; - props['data-precedence'] = precedence; - delete props.precedence; - return props; -} - -function stylePropsFromPreinitOptions(href, precedence, options) { - return { - rel: 'stylesheet', - href, - 'data-precedence': precedence, - crossOrigin: options.crossOrigin, - }; -} - -function createStyleResource(resources, href, precedence, props) { - const stylesMap = resources.stylesMap, - preloadsMap = resources.preloadsMap, - precedences = resources.precedences; // If this is the first time we've seen this precedence we encode it's position in our set even though - // we don't add the resource to this set yet - - let precedenceSet = precedences.get(precedence); - - if (!precedenceSet) { - precedenceSet = new Set(); - precedences.set(precedence, precedenceSet); - } - - let hint = preloadsMap.get(href); - - if (hint) { - // If a preload for this style Resource already exists there are certain props we want to adopt - // on the style Resource, primarily focussed on making sure the style network pathways utilize - // the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload - // and a stylesheet the stylesheet will make a new request even if the preload had already loaded - adoptPreloadPropsForStyleProps(props, hint.props); - } else { - const preloadResourceProps = preloadAsStylePropsFromProps(href, props); - hint = createPreloadResource( - resources, - href, - 'style', - preloadResourceProps - ); - - resources.explicitStylePreloads.add(hint); - } - - const resource = { - type: 'style', - href, - precedence, - flushed: false, - inShell: false, - props, - hint, - set: precedenceSet, - }; - stylesMap.set(href, resource); - return resource; -} - -function adoptPreloadPropsForStyleProps(resourceProps, preloadProps) { - if (resourceProps.crossOrigin == null) - resourceProps.crossOrigin = preloadProps.crossOrigin; - if (resourceProps.referrerPolicy == null) - resourceProps.referrerPolicy = preloadProps.referrerPolicy; - if (resourceProps.title == null) resourceProps.title = preloadProps.title; -} - -function scriptPropsFromPreinitOptions(src, options) { - return { - src, - async: true, - crossOrigin: options.crossOrigin, - integrity: options.integrity, - }; -} - -function scriptPropsFromRawProps(src, rawProps) { - const props = assign({}, rawProps); - - props.src = src; - return props; -} - -function createScriptResource(resources, src, props) { - const scriptsMap = resources.scriptsMap, - preloadsMap = resources.preloadsMap; - let hint = preloadsMap.get(src); - - if (hint) { - // If a preload for this style Resource already exists there are certain props we want to adopt - // on the style Resource, primarily focussed on making sure the style network pathways utilize - // the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload - // and a stylesheet the stylesheet will make a new request even if the preload had already loaded - adoptPreloadPropsForScriptProps(props, hint.props); - } else { - const preloadResourceProps = preloadAsScriptPropsFromProps(src, props); - hint = createPreloadResource( - resources, - src, - 'script', - preloadResourceProps - ); - - resources.explicitScriptPreloads.add(hint); - } - - const resource = { - type: 'script', - src, - flushed: false, - props, - hint, - }; - scriptsMap.set(src, resource); - return resource; -} - -function adoptPreloadPropsForScriptProps(resourceProps, preloadProps) { - if (resourceProps.crossOrigin == null) - resourceProps.crossOrigin = preloadProps.crossOrigin; - if (resourceProps.referrerPolicy == null) - resourceProps.referrerPolicy = preloadProps.referrerPolicy; - if (resourceProps.integrity == null) - resourceProps.integrity = preloadProps.integrity; -} - -function titlePropsFromRawProps(child, rawProps) { - const props = assign({}, rawProps); - - props.children = child; - return props; -} - -function resourcesFromElement(type, props) { - if (!currentResources) { - throw new Error( - '"currentResources" was expected to exist. This is a bug in React.' - ); - } - - const resources = currentResources; - - switch (type) { - case 'title': { - let child = props.children; - - if (Array.isArray(child) && child.length === 1) { - child = child[0]; - } - - if (typeof child === 'string' || typeof child === 'number') { - const key = 'title::' + child; - let resource = resources.headsMap.get(key); - - if (!resource) { - resource = { - type: 'title', - props: titlePropsFromRawProps(child, props), - flushed: false, - }; - resources.headsMap.set(key, resource); - resources.headResources.add(resource); - } - } - - return true; - } - - case 'meta': { - let key, propertyPath; - - if (typeof props.charSet === 'string') { - key = 'charSet'; - } else if (typeof props.content === 'string') { - const contentKey = '::' + props.content; - - if (typeof props.httpEquiv === 'string') { - key = 'httpEquiv::' + props.httpEquiv + contentKey; - } else if (typeof props.name === 'string') { - key = 'name::' + props.name + contentKey; - } else if (typeof props.itemProp === 'string') { - key = 'itemProp::' + props.itemProp + contentKey; - } else if (typeof props.property === 'string') { - const property = props.property; - key = 'property::' + property + contentKey; - propertyPath = property; - const parentPath = property.split(':').slice(0, -1).join(':'); - const parentResource = resources.structuredMetaKeys.get(parentPath); - - if (parentResource) { - key = parentResource.key + '::child::' + key; - } - } - } - - if (key) { - if (!resources.headsMap.has(key)) { - const resource = { - type: 'meta', - key, - props: assign({}, props), - flushed: false, - }; - resources.headsMap.set(key, resource); - - if (key === 'charSet') { - resources.charset = resource; - } else { - if (propertyPath) { - resources.structuredMetaKeys.set(propertyPath, resource); - } - - resources.headResources.add(resource); - } - } - } - - return true; - } - - case 'base': { - const target = props.target, - href = props.href; // We mirror the key construction on the client since we will likely unify - // this code in the future to better guarantee key semantics are identical - // in both environments - - let key = 'base'; - key += - typeof href === 'string' ? '[href="' + href + '"]' : ':not([href])'; - key += - typeof target === 'string' - ? '[target="' + target + '"]' - : ':not([target])'; - - if (!resources.headsMap.has(key)) { - const resource = { - type: 'base', - props: assign({}, props), - flushed: false, - }; - resources.headsMap.set(key, resource); - resources.bases.add(resource); - } - - return true; - } - } - - return false; -} // Construct a resource from link props. - -function resourcesFromLink(props) { - if (!currentResources) { - throw new Error( - '"currentResources" was expected to exist. This is a bug in React.' - ); - } - - const resources = currentResources; - const rel = props.rel, - href = props.href; - - if (!href || typeof href !== 'string' || !rel || typeof rel !== 'string') { - return false; - } - - let key = ''; - - switch (rel) { - case 'stylesheet': { - const onLoad = props.onLoad, - onError = props.onError, - precedence = props.precedence, - disabled = props.disabled; - - if ( - typeof precedence !== 'string' || - onLoad || - onError || - disabled != null - ) { - let preloadResource = resources.preloadsMap.get(href); - - if (!preloadResource) { - preloadResource = createPreloadResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - resources, - href, - 'style', - preloadAsStylePropsFromProps(href, props) - ); - - resources.usedStylePreloads.add(preloadResource); - } - - return false; - } else { - // We are able to convert this link element to a resource exclusively. We construct the relevant Resource - // and return true indicating that this link was fully consumed. - let resource = resources.stylesMap.get(href); - - if (resource); - else { - const resourceProps = stylePropsFromRawProps(href, precedence, props); - resource = createStyleResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - currentResources, - href, - precedence, - resourceProps - ); - resources.usedStylePreloads.add(resource.hint); - } - - if (resources.boundaryResources) { - resources.boundaryResources.add(resource); - } else { - resource.set.add(resource); - } - - return true; - } - } - - case 'preload': { - const as = props.as; - - switch (as) { - case 'script': - case 'style': - case 'font': { - let resource = resources.preloadsMap.get(href); - - if (resource); - else { - resource = createPreloadResource( - resources, - href, - as, - preloadPropsFromRawProps(href, as, props) - ); - - switch (as) { - case 'script': { - resources.explicitScriptPreloads.add(resource); - break; - } - - case 'style': { - resources.explicitStylePreloads.add(resource); - break; - } - - case 'font': { - resources.fontPreloads.add(resource); - break; - } - } - } - - return true; - } - } - - break; - } - } - - if (props.onLoad || props.onError) { - // When a link has these props we can't treat it is a Resource but if we rendered it on the - // server it would look like a Resource in the rendered html (the onLoad/onError aren't emitted) - // Instead we expect the client to insert them rather than hydrate them which also guarantees - // that the onLoad and onError won't fire before the event handlers are attached - return true; - } - - const sizes = typeof props.sizes === 'string' ? props.sizes : ''; - const media = typeof props.media === 'string' ? props.media : ''; - key = - 'rel:' + rel + '::href:' + href + '::sizes:' + sizes + '::media:' + media; - let resource = resources.headsMap.get(key); - - if (!resource) { - resource = { - type: 'link', - props: assign({}, props), - flushed: false, - }; - resources.headsMap.set(key, resource); - - switch (rel) { - case 'preconnect': - case 'dns-prefetch': { - resources.preconnects.add(resource); - break; - } - - default: { - resources.headResources.add(resource); - } - } - } - - return true; -} // Construct a resource from link props. - -function resourcesFromScript(props) { - if (!currentResources) { - throw new Error( - '"currentResources" was expected to exist. This is a bug in React.' - ); - } - - const resources = currentResources; - const src = props.src, - async = props.async, - onLoad = props.onLoad, - onError = props.onError; - - if (!src || typeof src !== 'string') { - return false; - } - - if (async) { - if (onLoad || onError) { - let preloadResource = resources.preloadsMap.get(src); - - if (!preloadResource) { - preloadResource = createPreloadResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - resources, - src, - 'script', - preloadAsScriptPropsFromProps(src, props) - ); - - resources.usedScriptPreloads.add(preloadResource); - } - } else { - let resource = resources.scriptsMap.get(src); - - if (resource); - else { - const resourceProps = scriptPropsFromRawProps(src, props); - resource = createScriptResource(resources, src, resourceProps); - resources.scripts.add(resource); - } - } - - return true; - } - - return false; -} -function hoistResources(resources, source) { - const currentBoundaryResources = resources.boundaryResources; - - if (currentBoundaryResources) { - source.forEach((resource) => currentBoundaryResources.add(resource)); - source.clear(); - } -} -function hoistResourcesToRoot(resources, boundaryResources) { - boundaryResources.forEach((resource) => resource.set.add(resource)); - boundaryResources.clear(); -} - -// The build script is at scripts/rollup/generate-inline-fizz-runtime.js. -// Run `yarn generate-inline-fizz-runtime` to generate. -const clientRenderBoundary = - '$RX=function(b,c,d,e){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),b._reactRetry&&b._reactRetry())};'; -const completeBoundary = - '$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};'; -const completeBoundaryWithStyles = - '$RM=new Map;\n$RR=function(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-precedence],style[data-precedence]"),d=0;e=f[d++];)m.set(e.dataset.precedence,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.precedence=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,\n"l"),r.bind(b,"e"));u.set(h,b);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"Resource failed to load"))};'; -const completeSegment = - '$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};'; - -const ReactDOMSharedInternals = - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; -function prepareToRender(resources) { - prepareToRenderResources(resources); - const previousHostDispatcher = ReactDOMCurrentDispatcher.current; - ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher; - return previousHostDispatcher; -} -function cleanupAfterRender(previousDispatcher) { - finishRenderingResources(); - ReactDOMCurrentDispatcher.current = previousDispatcher; -} // Used to distinguish these contexts from ones used in other renderers. - -const startInlineScript = stringToPrecomputedChunk('<script>'); -const endInlineScript = stringToPrecomputedChunk('</script>'); -const startScriptSrc = stringToPrecomputedChunk('<script src="'); -const startModuleSrc = stringToPrecomputedChunk('<script type="module" src="'); -const scriptIntegirty = stringToPrecomputedChunk('" integrity="'); -const endAsyncScript = stringToPrecomputedChunk('" async=""></script>'); -/** - * This escaping function is designed to work with bootstrapScriptContent only. - * because we know we are escaping the entire script. We can avoid for instance - * escaping html comment string sequences that are valid javascript as well because - * if there are no sebsequent <script sequences the html parser will never enter - * script data double escaped state (see: https://www.w3.org/TR/html53/syntax.html#script-data-double-escaped-state) - * - * While untrusted script content should be made safe before using this api it will - * ensure that the script cannot be early terminated or never terminated state - */ - -function escapeBootstrapScriptContent(scriptText) { - return ('' + scriptText).replace(scriptRegex, scriptReplacer); -} - -const scriptRegex = /(<\/|<)(s)(cript)/gi; - -const scriptReplacer = (match, prefix, s, suffix) => - '' + prefix + (s === 's' ? '\\u0073' : '\\u0053') + suffix; - -// Allows us to keep track of what we've already written so we can refer back to it. -function createResponseState( - identifierPrefix, - nonce, - bootstrapScriptContent, - bootstrapScripts, - bootstrapModules, - externalRuntimeConfig -) { - const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix; - const inlineScriptWithNonce = - nonce === undefined - ? startInlineScript - : stringToPrecomputedChunk( - '<script nonce="' + escapeTextForBrowser(nonce) + '">' - ); - const bootstrapChunks = []; - - if (bootstrapScriptContent !== undefined) { - bootstrapChunks.push( - inlineScriptWithNonce, - stringToChunk(escapeBootstrapScriptContent(bootstrapScriptContent)), - endInlineScript - ); - } - - if (bootstrapScripts !== undefined) { - for (let i = 0; i < bootstrapScripts.length; i++) { - const scriptConfig = bootstrapScripts[i]; - const src = - typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - const integrity = - typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - bootstrapChunks.push( - startScriptSrc, - stringToChunk(escapeTextForBrowser(src)) - ); - - if (integrity) { - bootstrapChunks.push( - scriptIntegirty, - stringToChunk(escapeTextForBrowser(integrity)) - ); - } - - bootstrapChunks.push(endAsyncScript); - } - } - - if (bootstrapModules !== undefined) { - for (let i = 0; i < bootstrapModules.length; i++) { - const scriptConfig = bootstrapModules[i]; - const src = - typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - const integrity = - typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - bootstrapChunks.push( - startModuleSrc, - stringToChunk(escapeTextForBrowser(src)) - ); - - if (integrity) { - bootstrapChunks.push( - scriptIntegirty, - stringToChunk(escapeTextForBrowser(integrity)) - ); - } - - bootstrapChunks.push(endAsyncScript); - } - } - - return { - bootstrapChunks: bootstrapChunks, - startInlineScript: inlineScriptWithNonce, - placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'), - segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'), - boundaryPrefix: idPrefix + 'B:', - idPrefix: idPrefix, - nextSuspenseID: 0, - sentCompleteSegmentFunction: false, - sentCompleteBoundaryFunction: false, - sentClientRenderFunction: false, - sentStyleInsertionFunction: false, - }; -} // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion -// modes. We only include the variants as they matter for the sake of our purposes. -// We don't actually provide the namespace therefore we use constants instead of the string. - -const ROOT_HTML_MODE = 0; // Used for the root most element tag. - -const HTML_MODE = 1; -const SVG_MODE = 2; -const MATHML_MODE = 3; -const HTML_TABLE_MODE = 4; -const HTML_TABLE_BODY_MODE = 5; -const HTML_TABLE_ROW_MODE = 6; -const HTML_COLGROUP_MODE = 7; // We have a greater than HTML_TABLE_MODE check elsewhere. If you add more cases here, make sure it -// still makes sense - -function createFormatContext(insertionMode, selectedValue, noscriptTagInScope) { - return { - insertionMode, - selectedValue, - noscriptTagInScope, - }; -} - -function createRootFormatContext(namespaceURI) { - const insertionMode = - namespaceURI === 'http://www.w3.org/2000/svg' - ? SVG_MODE - : namespaceURI === 'http://www.w3.org/1998/Math/MathML' - ? MATHML_MODE - : ROOT_HTML_MODE; - return createFormatContext(insertionMode, null, false); -} -function getChildFormatContext(parentContext, type, props) { - switch (type) { - case 'noscript': - return createFormatContext(HTML_MODE, null, true); - - case 'select': - return createFormatContext( - HTML_MODE, - props.value != null ? props.value : props.defaultValue, - parentContext.noscriptTagInScope - ); - - case 'svg': - return createFormatContext( - SVG_MODE, - null, - parentContext.noscriptTagInScope - ); - - case 'math': - return createFormatContext( - MATHML_MODE, - null, - parentContext.noscriptTagInScope - ); - - case 'foreignObject': - return createFormatContext( - HTML_MODE, - null, - parentContext.noscriptTagInScope - ); - // Table parents are special in that their children can only be created at all if they're - // wrapped in a table parent. So we need to encode that we're entering this mode. - - case 'table': - return createFormatContext( - HTML_TABLE_MODE, - null, - parentContext.noscriptTagInScope - ); - - case 'thead': - case 'tbody': - case 'tfoot': - return createFormatContext( - HTML_TABLE_BODY_MODE, - null, - parentContext.noscriptTagInScope - ); - - case 'colgroup': - return createFormatContext( - HTML_COLGROUP_MODE, - null, - parentContext.noscriptTagInScope - ); - - case 'tr': - return createFormatContext( - HTML_TABLE_ROW_MODE, - null, - parentContext.noscriptTagInScope - ); - } - - if (parentContext.insertionMode >= HTML_TABLE_MODE) { - // Whatever tag this was, it wasn't a table parent or other special parent, so we must have - // entered plain HTML again. - return createFormatContext( - HTML_MODE, - null, - parentContext.noscriptTagInScope - ); - } - - if (parentContext.insertionMode === ROOT_HTML_MODE) { - // We've emitted the root and is now in plain HTML mode. - return createFormatContext( - HTML_MODE, - null, - parentContext.noscriptTagInScope - ); - } - - return parentContext; -} -const UNINITIALIZED_SUSPENSE_BOUNDARY_ID = null; -function assignSuspenseBoundaryID(responseState) { - const generatedID = responseState.nextSuspenseID++; - return stringToPrecomputedChunk( - responseState.boundaryPrefix + generatedID.toString(16) - ); -} -function makeId(responseState, treeId, localId) { - const idPrefix = responseState.idPrefix; - let id = ':' + idPrefix + 'R' + treeId; // Unless this is the first id at this level, append a number at the end - // that represents the position of this useId hook among all the useId - // hooks for this fiber. - - if (localId > 0) { - id += 'H' + localId.toString(32); - } - - return id + ':'; -} - -function encodeHTMLTextNode(text) { - return escapeTextForBrowser(text); -} - -const textSeparator = stringToPrecomputedChunk('<!-- -->'); -function pushTextInstance(target, text, responseState, textEmbedded) { - if (text === '') { - // Empty text doesn't have a DOM node representation and the hydration is aware of this. - return textEmbedded; - } - - if (textEmbedded) { - target.push(textSeparator); - } - - target.push(stringToChunk(encodeHTMLTextNode(text))); - return true; -} // Called when Fizz is done with a Segment. Currently the only purpose is to conditionally -// emit a text separator when we don't know for sure it is safe to omit - -function pushSegmentFinale( - target, - responseState, - lastPushedText, - textEmbedded -) { - if (lastPushedText && textEmbedded) { - target.push(textSeparator); - } -} -const styleNameCache = new Map(); - -function processStyleName(styleName) { - const chunk = styleNameCache.get(styleName); - - if (chunk !== undefined) { - return chunk; - } - - const result = stringToPrecomputedChunk( - escapeTextForBrowser(hyphenateStyleName(styleName)) - ); - styleNameCache.set(styleName, result); - return result; -} - -const styleAttributeStart = stringToPrecomputedChunk(' style="'); -const styleAssign = stringToPrecomputedChunk(':'); -const styleSeparator = stringToPrecomputedChunk(';'); - -function pushStyle(target, responseState, style) { - if (typeof style !== 'object') { - throw new Error( - 'The `style` prop expects a mapping from style properties to values, ' + - "not a string. For example, style={{marginRight: spacing + 'em'}} when " + - 'using JSX.' - ); - } - - let isFirst = true; - - for (const styleName in style) { - if (!hasOwnProperty.call(style, styleName)) { - continue; - } // If you provide unsafe user data here they can inject arbitrary CSS - // which may be problematic (I couldn't repro this): - // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet - // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/ - // This is not an XSS hole but instead a potential CSS injection issue - // which has lead to a greater discussion about how we're going to - // trust URLs moving forward. See #2115901 - - const styleValue = style[styleName]; - - if ( - styleValue == null || - typeof styleValue === 'boolean' || - styleValue === '' - ) { - // TODO: We used to set empty string as a style with an empty value. Does that ever make sense? - continue; - } - - let nameChunk; - let valueChunk; - const isCustomProperty = styleName.indexOf('--') === 0; - - if (isCustomProperty) { - nameChunk = stringToChunk(escapeTextForBrowser(styleName)); - - valueChunk = stringToChunk( - escapeTextForBrowser(('' + styleValue).trim()) - ); - } else { - nameChunk = processStyleName(styleName); - - if (typeof styleValue === 'number') { - if ( - styleValue !== 0 && - !hasOwnProperty.call(isUnitlessNumber, styleName) - ) { - valueChunk = stringToChunk(styleValue + 'px'); // Presumes implicit 'px' suffix for unitless numbers - } else { - valueChunk = stringToChunk('' + styleValue); - } - } else { - valueChunk = stringToChunk( - escapeTextForBrowser(('' + styleValue).trim()) - ); - } - } - - if (isFirst) { - isFirst = false; // If it's first, we don't need any separators prefixed. - - target.push(styleAttributeStart, nameChunk, styleAssign, valueChunk); - } else { - target.push(styleSeparator, nameChunk, styleAssign, valueChunk); - } - } - - if (!isFirst) { - target.push(attributeEnd); - } -} - -const attributeSeparator = stringToPrecomputedChunk(' '); -const attributeAssign = stringToPrecomputedChunk('="'); -const attributeEnd = stringToPrecomputedChunk('"'); -const attributeEmptyString = stringToPrecomputedChunk('=""'); - -function pushAttribute(target, responseState, name, value) { - switch (name) { - case 'style': { - pushStyle(target, responseState, value); - return; - } - - case 'defaultValue': - case 'defaultChecked': // These shouldn't be set as attributes on generic HTML elements. - - case 'innerHTML': // Must use dangerouslySetInnerHTML instead. - - case 'suppressContentEditableWarning': - case 'suppressHydrationWarning': - // Ignored. These are built-in to React on the client. - return; - } - - if ( - // shouldIgnoreAttribute - // We have already filtered out null/undefined and reserved words. - name.length > 2 && - (name[0] === 'o' || name[0] === 'O') && - (name[1] === 'n' || name[1] === 'N') - ) { - return; - } - - const propertyInfo = getPropertyInfo(name); - - if (propertyInfo !== null) { - // shouldRemoveAttribute - switch (typeof value) { - case 'function': - case 'symbol': - // eslint-disable-line - return; - - case 'boolean': { - if (!propertyInfo.acceptsBooleans) { - return; - } - } - } - - const attributeName = propertyInfo.attributeName; - const attributeNameChunk = stringToChunk(attributeName); // TODO: If it's known we can cache the chunk. - - switch (propertyInfo.type) { - case BOOLEAN: - if (value) { - target.push( - attributeSeparator, - attributeNameChunk, - attributeEmptyString - ); - } - - return; - - case OVERLOADED_BOOLEAN: - if (value === true) { - target.push( - attributeSeparator, - attributeNameChunk, - attributeEmptyString - ); - } else if (value === false); - else { - target.push( - attributeSeparator, - attributeNameChunk, - attributeAssign, - stringToChunk(escapeTextForBrowser(value)), - attributeEnd - ); - } - - return; - - case NUMERIC: - if (!isNaN(value)) { - target.push( - attributeSeparator, - attributeNameChunk, - attributeAssign, - stringToChunk(escapeTextForBrowser(value)), - attributeEnd - ); - } - - break; - - case POSITIVE_NUMERIC: - if (!isNaN(value) && value >= 1) { - target.push( - attributeSeparator, - attributeNameChunk, - attributeAssign, - stringToChunk(escapeTextForBrowser(value)), - attributeEnd - ); - } - - break; - - default: - if (propertyInfo.sanitizeURL) { - value = '' + value; - } - - target.push( - attributeSeparator, - attributeNameChunk, - attributeAssign, - stringToChunk(escapeTextForBrowser(value)), - attributeEnd - ); - } - } else if (isAttributeNameSafe(name)) { - // shouldRemoveAttribute - switch (typeof value) { - case 'function': - case 'symbol': - // eslint-disable-line - return; - - case 'boolean': { - const prefix = name.toLowerCase().slice(0, 5); - - if (prefix !== 'data-' && prefix !== 'aria-') { - return; - } - } - } - - target.push( - attributeSeparator, - stringToChunk(name), - attributeAssign, - stringToChunk(escapeTextForBrowser(value)), - attributeEnd - ); - } -} - -const endOfStartTag = stringToPrecomputedChunk('>'); -const endOfStartTagSelfClosing = stringToPrecomputedChunk('/>'); - -function pushInnerHTML(target, innerHTML, children) { - if (innerHTML != null) { - if (children != null) { - throw new Error( - 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' - ); - } - - if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) { - throw new Error( - '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + - 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' + - 'for more information.' - ); - } - - const html = innerHTML.__html; - - if (html !== null && html !== undefined) { - target.push(stringToChunk('' + html)); - } - } -} // TODO: Move these to ResponseState so that we warn for every request. - -function pushStartSelect(target, props, responseState) { - target.push(startChunkForTag('select')); - let children = null; - let innerHTML = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - // TODO: This doesn't really make sense for select since it can't use the controlled - // value in the innerHTML. - innerHTML = propValue; - break; - - case 'defaultValue': - case 'value': - // These are set on the Context instead and applied to the nested options. - break; - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); - pushInnerHTML(target, innerHTML, children); - return children; -} - -function flattenOptionChildren(children) { - let content = ''; // Flatten children and warn if they aren't strings or numbers; - // invalid types are ignored. - - Children.forEach(children, function (child) { - if (child == null) { - return; - } - - content += child; - }); - return content; -} - -const selectedMarkerAttribute = stringToPrecomputedChunk(' selected=""'); - -function pushStartOption(target, props, responseState, formatContext) { - const selectedValue = formatContext.selectedValue; - target.push(startChunkForTag('option')); - let children = null; - let value = null; - let selected = null; - let innerHTML = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'selected': - // ignore - selected = propValue; - - break; - - case 'dangerouslySetInnerHTML': - innerHTML = propValue; - break; - // eslint-disable-next-line-no-fallthrough - - case 'value': - value = propValue; - // We intentionally fallthrough to also set the attribute on the node. - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - if (selectedValue != null) { - let stringValue; - - if (value !== null) { - stringValue = '' + value; - } else { - stringValue = flattenOptionChildren(children); - } - - if (isArray(selectedValue)) { - // multiple - for (let i = 0; i < selectedValue.length; i++) { - const v = '' + selectedValue[i]; - - if (v === stringValue) { - target.push(selectedMarkerAttribute); - break; - } - } - } else { - if ('' + selectedValue === stringValue) { - target.push(selectedMarkerAttribute); - } - } - } else if (selected) { - target.push(selectedMarkerAttribute); - } - - target.push(endOfStartTag); - pushInnerHTML(target, innerHTML, children); - return children; -} - -function pushInput(target, props, responseState) { - target.push(startChunkForTag('input')); - let value = null; - let defaultValue = null; - let checked = null; - let defaultChecked = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - case 'dangerouslySetInnerHTML': - throw new Error( - 'input' + - ' is a self-closing tag and must neither have `children` nor ' + - 'use `dangerouslySetInnerHTML`.' - ); - // eslint-disable-next-line-no-fallthrough - - case 'defaultChecked': - defaultChecked = propValue; - break; - - case 'defaultValue': - defaultValue = propValue; - break; - - case 'checked': - checked = propValue; - break; - - case 'value': - value = propValue; - break; - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - if (checked !== null) { - pushAttribute(target, responseState, 'checked', checked); - } else if (defaultChecked !== null) { - pushAttribute(target, responseState, 'checked', defaultChecked); - } - - if (value !== null) { - pushAttribute(target, responseState, 'value', value); - } else if (defaultValue !== null) { - pushAttribute(target, responseState, 'value', defaultValue); - } - - target.push(endOfStartTagSelfClosing); - return null; -} - -function pushStartTextArea(target, props, responseState) { - target.push(startChunkForTag('textarea')); - let value = null; - let defaultValue = null; - let children = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'value': - value = propValue; - break; - - case 'defaultValue': - defaultValue = propValue; - break; - - case 'dangerouslySetInnerHTML': - throw new Error( - '`dangerouslySetInnerHTML` does not make sense on <textarea>.' - ); - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - if (value === null && defaultValue !== null) { - value = defaultValue; - } - - target.push(endOfStartTag); // TODO (yungsters): Remove support for children content in <textarea>. - - if (children != null) { - if (value != null) { - throw new Error( - 'If you supply `defaultValue` on a <textarea>, do not pass children.' - ); - } - - if (isArray(children)) { - if (children.length > 1) { - throw new Error('<textarea> can only have at most one child.'); - } // TODO: remove the coercion and the DEV check below because it will - - value = '' + children[0]; - } - - value = '' + children; - } - - if (typeof value === 'string' && value[0] === '\n') { - // text/html ignores the first character in these tags if it's a newline - // Prefer to break application/xml over text/html (for now) by adding - // a newline specifically to get eaten by the parser. (Alternately for - // textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first - // \r is normalized out by HTMLTextAreaElement#value.) - // See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre> - // See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions> - // See: <http://www.w3.org/TR/html5/syntax.html#newlines> - // See: Parsing of "textarea" "listing" and "pre" elements - // from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody> - target.push(leadingNewline); - } // ToString and push directly instead of recurse over children. - // We don't really support complex children in the value anyway. - // This also currently avoids a trailing comment node which breaks textarea. - - if (value !== null) { - target.push(stringToChunk(encodeHTMLTextNode('' + value))); - } - - return null; -} - -function pushBase( - target, - props, - responseState, - textEmbedded, - noscriptTagInScope -) { - if (!noscriptTagInScope && resourcesFromElement('base', props)) { - if (textEmbedded) { - // This link follows text but we aren't writing a tag. while not as efficient as possible we need - // to be safe and assume text will follow by inserting a textSeparator - target.push(textSeparator); - } // We have converted this link exclusively to a resource and no longer - // need to emit it - - return null; - } - - return pushSelfClosing(target, props, 'base', responseState); -} - -function pushMeta( - target, - props, - responseState, - textEmbedded, - noscriptTagInScope -) { - if (!noscriptTagInScope && resourcesFromElement('meta', props)) { - if (textEmbedded) { - // This link follows text but we aren't writing a tag. while not as efficient as possible we need - // to be safe and assume text will follow by inserting a textSeparator - target.push(textSeparator); - } // We have converted this link exclusively to a resource and no longer - // need to emit it - - return null; - } - - return pushSelfClosing(target, props, 'meta', responseState); -} - -function pushLink( - target, - props, - responseState, - textEmbedded, - noscriptTagInScope -) { - if (!noscriptTagInScope && resourcesFromLink(props)) { - if (textEmbedded) { - // This link follows text but we aren't writing a tag. while not as efficient as possible we need - // to be safe and assume text will follow by inserting a textSeparator - target.push(textSeparator); - } // We have converted this link exclusively to a resource and no longer - // need to emit it - - return null; - } - - return pushLinkImpl(target, props, responseState); -} - -function pushLinkImpl(target, props, responseState) { - const isStylesheet = props.rel === 'stylesheet'; - target.push(startChunkForTag('link')); - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - case 'dangerouslySetInnerHTML': - throw new Error( - 'link' + - ' is a self-closing tag and must neither have `children` nor ' + - 'use `dangerouslySetInnerHTML`.' - ); - - case 'precedence': { - if (isStylesheet) { - // precedence is a reversed property for stylesheets to opt-into resource semantcs - continue; - } // intentionally fall through - } - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTagSelfClosing); - return null; -} - -function pushSelfClosing(target, props, tag, responseState) { - target.push(startChunkForTag(tag)); - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - case 'dangerouslySetInnerHTML': - throw new Error( - tag + - ' is a self-closing tag and must neither have `children` nor ' + - 'use `dangerouslySetInnerHTML`.' - ); - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTagSelfClosing); - return null; -} - -function pushStartMenuItem(target, props, responseState) { - target.push(startChunkForTag('menuitem')); - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - case 'dangerouslySetInnerHTML': - throw new Error( - 'menuitems cannot have `children` nor `dangerouslySetInnerHTML`.' - ); - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); - return null; -} - -function pushTitle(target, props, responseState, noscriptTagInScope) { - if (!noscriptTagInScope && resourcesFromElement('title', props)) { - // We have converted this link exclusively to a resource and no longer - // need to emit it - return null; - } - - return pushTitleImpl(target, props, responseState); -} - -function pushTitleImpl(target, props, responseState) { - target.push(startChunkForTag('title')); - let children = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - throw new Error( - '`dangerouslySetInnerHTML` does not make sense on <title>.' - ); - // eslint-disable-next-line-no-fallthrough - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); - const child = - Array.isArray(children) && children.length < 2 - ? children[0] || null - : children; - - if (typeof child === 'string' || typeof child === 'number') { - target.push(stringToChunk(escapeTextForBrowser(child))); - } - - target.push(endTag1, stringToChunk('title'), endTag2); - return null; -} - -function pushStartHead(target, preamble, props, tag, responseState) { - return pushStartGenericElement(preamble, props, tag, responseState); -} - -function pushStartHtml( - target, - preamble, - props, - tag, - responseState, - formatContext -) { - target = preamble; - - if (formatContext.insertionMode === ROOT_HTML_MODE) { - // If we're rendering the html tag and we're at the root (i.e. not in foreignObject) - // then we also emit the DOCTYPE as part of the root content as a convenience for - // rendering the whole document. - target.push(DOCTYPE); - } - - return pushStartGenericElement(target, props, tag, responseState); -} - -function pushScript( - target, - props, - responseState, - textEmbedded, - noscriptTagInScope -) { - if (!noscriptTagInScope && resourcesFromScript(props)) { - if (textEmbedded) { - // This link follows text but we aren't writing a tag. while not as efficient as possible we need - // to be safe and assume text will follow by inserting a textSeparator - target.push(textSeparator); - } // We have converted this link exclusively to a resource and no longer - // need to emit it - - return null; - } - - return pushScriptImpl(target, props, responseState); -} - -function pushScriptImpl(target, props, responseState) { - target.push(startChunkForTag('script')); - let children = null; - let innerHTML = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - innerHTML = propValue; - break; - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); - - pushInnerHTML(target, innerHTML, children); - - if (typeof children === 'string') { - target.push(stringToChunk(encodeHTMLTextNode(children))); - } - - target.push(endTag1, stringToChunk('script'), endTag2); - return null; -} - -function pushStartGenericElement(target, props, tag, responseState) { - target.push(startChunkForTag(tag)); - let children = null; - let innerHTML = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - innerHTML = propValue; - break; - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); - pushInnerHTML(target, innerHTML, children); - - if (typeof children === 'string') { - // Special case children as a string to avoid the unnecessary comment. - // TODO: Remove this special case after the general optimization is in place. - target.push(stringToChunk(encodeHTMLTextNode(children))); - return null; - } - - return children; -} - -function pushStartCustomElement(target, props, tag, responseState) { - target.push(startChunkForTag(tag)); - let children = null; - let innerHTML = null; - - for (let propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - let propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - if (typeof propValue === 'function' || typeof propValue === 'object') { - // It is normal to render functions and objects on custom elements when - // client rendering, but when server rendering the output isn't useful, - // so skip it. - continue; - } - - if (propValue === false) { - continue; - } - - if (propValue === true) { - propValue = ''; - } - - if (propKey === 'className') { - // className gets rendered as class on the client, so it should be - // rendered as class on the server. - propKey = 'class'; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - innerHTML = propValue; - break; - - case 'style': - pushStyle(target, responseState, propValue); - break; - - case 'suppressContentEditableWarning': - case 'suppressHydrationWarning': - // Ignored. These are built-in to React on the client. - break; - - default: - if ( - isAttributeNameSafe(propKey) && - typeof propValue !== 'function' && - typeof propValue !== 'symbol' - ) { - target.push( - attributeSeparator, - stringToChunk(propKey), - attributeAssign, - stringToChunk(escapeTextForBrowser(propValue)), - attributeEnd - ); - } - - break; - } - } - } - - target.push(endOfStartTag); - pushInnerHTML(target, innerHTML, children); - return children; -} - -const leadingNewline = stringToPrecomputedChunk('\n'); - -function pushStartPreformattedElement(target, props, tag, responseState) { - target.push(startChunkForTag(tag)); - let children = null; - let innerHTML = null; - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'children': - children = propValue; - break; - - case 'dangerouslySetInnerHTML': - innerHTML = propValue; - break; - - default: - pushAttribute(target, responseState, propKey, propValue); - break; - } - } - } - - target.push(endOfStartTag); // text/html ignores the first character in these tags if it's a newline - // Prefer to break application/xml over text/html (for now) by adding - // a newline specifically to get eaten by the parser. (Alternately for - // textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first - // \r is normalized out by HTMLTextAreaElement#value.) - // See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre> - // See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions> - // See: <http://www.w3.org/TR/html5/syntax.html#newlines> - // See: Parsing of "textarea" "listing" and "pre" elements - // from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody> - // TODO: This doesn't deal with the case where the child is an array - // or component that returns a string. - - if (innerHTML != null) { - if (children != null) { - throw new Error( - 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' - ); - } - - if (typeof innerHTML !== 'object' || !('__html' in innerHTML)) { - throw new Error( - '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + - 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' + - 'for more information.' - ); - } - - const html = innerHTML.__html; - - if (html !== null && html !== undefined) { - if (typeof html === 'string' && html.length > 0 && html[0] === '\n') { - target.push(leadingNewline, stringToChunk(html)); - } else { - target.push(stringToChunk('' + html)); - } - } - } - - if (typeof children === 'string' && children[0] === '\n') { - target.push(leadingNewline); - } - - return children; -} // We accept any tag to be rendered but since this gets injected into arbitrary -// HTML, we want to make sure that it's a safe tag. -// http://www.w3.org/TR/REC-xml/#NT-Name - -const VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset - -const validatedTagCache = new Map(); - -function startChunkForTag(tag) { - let tagStartChunk = validatedTagCache.get(tag); - - if (tagStartChunk === undefined) { - if (!VALID_TAG_REGEX.test(tag)) { - throw new Error('Invalid tag: ' + tag); - } - - tagStartChunk = stringToPrecomputedChunk('<' + tag); - validatedTagCache.set(tag, tagStartChunk); - } - - return tagStartChunk; -} - -const DOCTYPE = stringToPrecomputedChunk('<!DOCTYPE html>'); -function pushStartInstance( - target, - preamble, - type, - props, - responseState, - formatContext, - textEmbedded -) { - switch (type) { - // Special tags - case 'select': - return pushStartSelect(target, props, responseState); - - case 'option': - return pushStartOption(target, props, responseState, formatContext); - - case 'textarea': - return pushStartTextArea(target, props, responseState); - - case 'input': - return pushInput(target, props, responseState); - - case 'menuitem': - return pushStartMenuItem(target, props, responseState); - - case 'title': - return pushTitle( - target, - props, - responseState, - formatContext.noscriptTagInScope - ); - - case 'link': - return pushLink( - target, - props, - responseState, - textEmbedded, - formatContext.noscriptTagInScope - ); - - case 'script': - return pushScript( - target, - props, - responseState, - textEmbedded, - formatContext.noscriptTagInScope - ); - - case 'meta': - return pushMeta( - target, - props, - responseState, - textEmbedded, - formatContext.noscriptTagInScope - ); - - case 'base': - return pushBase( - target, - props, - responseState, - textEmbedded, - formatContext.noscriptTagInScope - ); - // Newline eating tags - - case 'listing': - case 'pre': { - return pushStartPreformattedElement(target, props, type, responseState); - } - // Omitted close tags - - case 'area': - case 'br': - case 'col': - case 'embed': - case 'hr': - case 'img': - case 'keygen': - case 'param': - case 'source': - case 'track': - case 'wbr': { - return pushSelfClosing(target, props, type, responseState); - } - // These are reserved SVG and MathML elements, that are never custom elements. - // https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts - - case 'annotation-xml': - case 'color-profile': - case 'font-face': - case 'font-face-src': - case 'font-face-uri': - case 'font-face-format': - case 'font-face-name': - case 'missing-glyph': { - return pushStartGenericElement(target, props, type, responseState); - } - // Preamble start tags - - case 'head': - return pushStartHead(target, preamble, props, type, responseState); - - case 'html': { - return pushStartHtml( - target, - preamble, - props, - type, - responseState, - formatContext - ); - } - - default: { - if (type.indexOf('-') === -1 && typeof props.is !== 'string') { - // Generic element - return pushStartGenericElement(target, props, type, responseState); - } else { - // Custom element - return pushStartCustomElement(target, props, type, responseState); - } - } - } -} -const endTag1 = stringToPrecomputedChunk('</'); -const endTag2 = stringToPrecomputedChunk('>'); -function pushEndInstance(target, postamble, type, props) { - switch (type) { - // When float is on we expect title and script tags to always be pushed in - // a unit and never return children. when we end up pushing the end tag we - // want to ensure there is no extra closing tag pushed - case 'title': - case 'script': - // Omitted close tags - // TODO: Instead of repeating this switch we could try to pass a flag from above. - // That would require returning a tuple. Which might be ok if it gets inlined. - // eslint-disable-next-line-no-fallthrough - - case 'area': - case 'base': - case 'br': - case 'col': - case 'embed': - case 'hr': - case 'img': - case 'input': - case 'keygen': - case 'link': - case 'meta': - case 'param': - case 'source': - case 'track': - case 'wbr': { - // No close tag needed. - return; - } - // Postamble end tags - - case 'body': { - { - postamble.unshift(endTag1, stringToChunk(type), endTag2); - return; - } - } - - case 'html': { - postamble.push(endTag1, stringToChunk(type), endTag2); - return; - } - } - - target.push(endTag1, stringToChunk(type), endTag2); -} -function writeCompletedRoot(destination, responseState) { - const bootstrapChunks = responseState.bootstrapChunks; - let i = 0; - - for (; i < bootstrapChunks.length - 1; i++) { - writeChunk(destination, bootstrapChunks[i]); - } - - if (i < bootstrapChunks.length) { - return writeChunkAndReturn(destination, bootstrapChunks[i]); - } - - return true; -} // Structural Nodes -// A placeholder is a node inside a hidden partial tree that can be filled in later, but before -// display. It's never visible to users. We use the template tag because it can be used in every -// type of parent. <script> tags also work in every other tag except <colgroup>. - -const placeholder1 = stringToPrecomputedChunk('<template id="'); -const placeholder2 = stringToPrecomputedChunk('"></template>'); -function writePlaceholder(destination, responseState, id) { - writeChunk(destination, placeholder1); - writeChunk(destination, responseState.placeholderPrefix); - const formattedID = stringToChunk(id.toString(16)); - writeChunk(destination, formattedID); - return writeChunkAndReturn(destination, placeholder2); -} // Suspense boundaries are encoded as comments. - -const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->'); -const startPendingSuspenseBoundary1 = stringToPrecomputedChunk( - '<!--$?--><template id="' -); -const startPendingSuspenseBoundary2 = stringToPrecomputedChunk('"></template>'); -const startClientRenderedSuspenseBoundary = - stringToPrecomputedChunk('<!--$!-->'); -const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->'); -const clientRenderedSuspenseBoundaryError1 = - stringToPrecomputedChunk('<template'); -const clientRenderedSuspenseBoundaryErrorAttrInterstitial = - stringToPrecomputedChunk('"'); -const clientRenderedSuspenseBoundaryError1A = - stringToPrecomputedChunk(' data-dgst="'); -const clientRenderedSuspenseBoundaryError2 = - stringToPrecomputedChunk('></template>'); -function writeStartCompletedSuspenseBoundary(destination, responseState) { - return writeChunkAndReturn(destination, startCompletedSuspenseBoundary); -} -function writeStartPendingSuspenseBoundary(destination, responseState, id) { - writeChunk(destination, startPendingSuspenseBoundary1); - - if (id === null) { - throw new Error( - 'An ID must have been assigned before we can complete the boundary.' - ); - } - - writeChunk(destination, id); - return writeChunkAndReturn(destination, startPendingSuspenseBoundary2); -} -function writeStartClientRenderedSuspenseBoundary( - destination, - responseState, - errorDigest, - errorMesssage, - errorComponentStack -) { - let result; - result = writeChunkAndReturn( - destination, - startClientRenderedSuspenseBoundary - ); - writeChunk(destination, clientRenderedSuspenseBoundaryError1); - - if (errorDigest) { - writeChunk(destination, clientRenderedSuspenseBoundaryError1A); - writeChunk(destination, stringToChunk(escapeTextForBrowser(errorDigest))); - writeChunk( - destination, - clientRenderedSuspenseBoundaryErrorAttrInterstitial - ); - } - - result = writeChunkAndReturn( - destination, - clientRenderedSuspenseBoundaryError2 - ); - return result; -} -function writeEndCompletedSuspenseBoundary(destination, responseState) { - return writeChunkAndReturn(destination, endSuspenseBoundary); -} -function writeEndPendingSuspenseBoundary(destination, responseState) { - return writeChunkAndReturn(destination, endSuspenseBoundary); -} -function writeEndClientRenderedSuspenseBoundary(destination, responseState) { - return writeChunkAndReturn(destination, endSuspenseBoundary); -} -const startSegmentHTML = stringToPrecomputedChunk('<div hidden id="'); -const startSegmentHTML2 = stringToPrecomputedChunk('">'); -const endSegmentHTML = stringToPrecomputedChunk('</div>'); -const startSegmentSVG = stringToPrecomputedChunk( - '<svg aria-hidden="true" style="display:none" id="' -); -const startSegmentSVG2 = stringToPrecomputedChunk('">'); -const endSegmentSVG = stringToPrecomputedChunk('</svg>'); -const startSegmentMathML = stringToPrecomputedChunk( - '<math aria-hidden="true" style="display:none" id="' -); -const startSegmentMathML2 = stringToPrecomputedChunk('">'); -const endSegmentMathML = stringToPrecomputedChunk('</math>'); -const startSegmentTable = stringToPrecomputedChunk('<table hidden id="'); -const startSegmentTable2 = stringToPrecomputedChunk('">'); -const endSegmentTable = stringToPrecomputedChunk('</table>'); -const startSegmentTableBody = stringToPrecomputedChunk( - '<table hidden><tbody id="' -); -const startSegmentTableBody2 = stringToPrecomputedChunk('">'); -const endSegmentTableBody = stringToPrecomputedChunk('</tbody></table>'); -const startSegmentTableRow = stringToPrecomputedChunk('<table hidden><tr id="'); -const startSegmentTableRow2 = stringToPrecomputedChunk('">'); -const endSegmentTableRow = stringToPrecomputedChunk('</tr></table>'); -const startSegmentColGroup = stringToPrecomputedChunk( - '<table hidden><colgroup id="' -); -const startSegmentColGroup2 = stringToPrecomputedChunk('">'); -const endSegmentColGroup = stringToPrecomputedChunk('</colgroup></table>'); -function writeStartSegment(destination, responseState, formatContext, id) { - switch (formatContext.insertionMode) { - case ROOT_HTML_MODE: - case HTML_MODE: { - writeChunk(destination, startSegmentHTML); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentHTML2); - } - - case SVG_MODE: { - writeChunk(destination, startSegmentSVG); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentSVG2); - } - - case MATHML_MODE: { - writeChunk(destination, startSegmentMathML); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentMathML2); - } - - case HTML_TABLE_MODE: { - writeChunk(destination, startSegmentTable); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentTable2); - } - // TODO: For the rest of these, there will be extra wrapper nodes that never - // get deleted from the document. We need to delete the table too as part - // of the injected scripts. They are invisible though so it's not too terrible - // and it's kind of an edge case to suspend in a table. Totally supported though. - - case HTML_TABLE_BODY_MODE: { - writeChunk(destination, startSegmentTableBody); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentTableBody2); - } - - case HTML_TABLE_ROW_MODE: { - writeChunk(destination, startSegmentTableRow); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentTableRow2); - } - - case HTML_COLGROUP_MODE: { - writeChunk(destination, startSegmentColGroup); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, stringToChunk(id.toString(16))); - return writeChunkAndReturn(destination, startSegmentColGroup2); - } - - default: { - throw new Error('Unknown insertion mode. This is a bug in React.'); - } - } -} -function writeEndSegment(destination, formatContext) { - switch (formatContext.insertionMode) { - case ROOT_HTML_MODE: - case HTML_MODE: { - return writeChunkAndReturn(destination, endSegmentHTML); - } - - case SVG_MODE: { - return writeChunkAndReturn(destination, endSegmentSVG); - } - - case MATHML_MODE: { - return writeChunkAndReturn(destination, endSegmentMathML); - } - - case HTML_TABLE_MODE: { - return writeChunkAndReturn(destination, endSegmentTable); - } - - case HTML_TABLE_BODY_MODE: { - return writeChunkAndReturn(destination, endSegmentTableBody); - } - - case HTML_TABLE_ROW_MODE: { - return writeChunkAndReturn(destination, endSegmentTableRow); - } - - case HTML_COLGROUP_MODE: { - return writeChunkAndReturn(destination, endSegmentColGroup); - } - - default: { - throw new Error('Unknown insertion mode. This is a bug in React.'); - } - } -} -const completeSegmentScript1Full = stringToPrecomputedChunk( - completeSegment + ';$RS("' -); -const completeSegmentScript1Partial = stringToPrecomputedChunk('$RS("'); -const completeSegmentScript2 = stringToPrecomputedChunk('","'); -const completeSegmentScript3 = stringToPrecomputedChunk('")</script>'); -function writeCompletedSegmentInstruction( - destination, - responseState, - contentSegmentID -) { - writeChunk(destination, responseState.startInlineScript); - - if (!responseState.sentCompleteSegmentFunction) { - // The first time we write this, we'll need to include the full implementation. - responseState.sentCompleteSegmentFunction = true; - writeChunk(destination, completeSegmentScript1Full); - } else { - // Future calls can just reuse the same function. - writeChunk(destination, completeSegmentScript1Partial); - } - - writeChunk(destination, responseState.segmentPrefix); - const formattedID = stringToChunk(contentSegmentID.toString(16)); - writeChunk(destination, formattedID); - writeChunk(destination, completeSegmentScript2); - writeChunk(destination, responseState.placeholderPrefix); - writeChunk(destination, formattedID); - return writeChunkAndReturn(destination, completeSegmentScript3); -} -const completeBoundaryScript1Full = stringToPrecomputedChunk( - completeBoundary + ';$RC("' -); -const completeBoundaryScript1Partial = stringToPrecomputedChunk('$RC("'); -const completeBoundaryWithStylesScript1FullBoth = stringToPrecomputedChunk( - completeBoundary + ';' + completeBoundaryWithStyles + ';$RR("' -); -const completeBoundaryWithStylesScript1FullPartial = stringToPrecomputedChunk( - completeBoundaryWithStyles + ';$RR("' -); -const completeBoundaryWithStylesScript1Partial = - stringToPrecomputedChunk('$RR("'); -const completeBoundaryScript2 = stringToPrecomputedChunk('","'); -const completeBoundaryScript2a = stringToPrecomputedChunk('",'); -const completeBoundaryScript3 = stringToPrecomputedChunk('"'); -const completeBoundaryScript4 = stringToPrecomputedChunk(')</script>'); -function writeCompletedBoundaryInstruction( - destination, - responseState, - boundaryID, - contentSegmentID, - boundaryResources -) { - let hasStyleDependencies; - - { - hasStyleDependencies = hasStyleResourceDependencies(boundaryResources); - } - - writeChunk(destination, responseState.startInlineScript); - - if (hasStyleDependencies) { - if (!responseState.sentCompleteBoundaryFunction) { - responseState.sentCompleteBoundaryFunction = true; - responseState.sentStyleInsertionFunction = true; - writeChunk(destination, completeBoundaryWithStylesScript1FullBoth); - } else if (!responseState.sentStyleInsertionFunction) { - responseState.sentStyleInsertionFunction = true; - writeChunk(destination, completeBoundaryWithStylesScript1FullPartial); - } else { - writeChunk(destination, completeBoundaryWithStylesScript1Partial); - } - } else { - if (!responseState.sentCompleteBoundaryFunction) { - responseState.sentCompleteBoundaryFunction = true; - writeChunk(destination, completeBoundaryScript1Full); - } else { - writeChunk(destination, completeBoundaryScript1Partial); - } - } - - if (boundaryID === null) { - throw new Error( - 'An ID must have been assigned before we can complete the boundary.' - ); - } - - const formattedContentID = stringToChunk(contentSegmentID.toString(16)); - writeChunk(destination, boundaryID); - writeChunk(destination, completeBoundaryScript2); - writeChunk(destination, responseState.segmentPrefix); - writeChunk(destination, formattedContentID); - - if (hasStyleDependencies) { - writeChunk(destination, completeBoundaryScript2a); - writeStyleResourceDependencies(destination, boundaryResources); - } else { - writeChunk(destination, completeBoundaryScript3); - } - - return writeChunkAndReturn(destination, completeBoundaryScript4); -} -const clientRenderScript1Full = stringToPrecomputedChunk( - clientRenderBoundary + ';$RX("' -); -const clientRenderScript1Partial = stringToPrecomputedChunk('$RX("'); -const clientRenderScript1A = stringToPrecomputedChunk('"'); -const clientRenderScript2 = stringToPrecomputedChunk(')</script>'); -const clientRenderErrorScriptArgInterstitial = stringToPrecomputedChunk(','); -function writeClientRenderBoundaryInstruction( - destination, - responseState, - boundaryID, - errorDigest, - errorMessage, - errorComponentStack -) { - writeChunk(destination, responseState.startInlineScript); - - if (!responseState.sentClientRenderFunction) { - // The first time we write this, we'll need to include the full implementation. - responseState.sentClientRenderFunction = true; - writeChunk(destination, clientRenderScript1Full); - } else { - // Future calls can just reuse the same function. - writeChunk(destination, clientRenderScript1Partial); - } - - if (boundaryID === null) { - throw new Error( - 'An ID must have been assigned before we can complete the boundary.' - ); - } - - writeChunk(destination, boundaryID); - writeChunk(destination, clientRenderScript1A); - - if (errorDigest || errorMessage || errorComponentStack) { - writeChunk(destination, clientRenderErrorScriptArgInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSStringsForInstructionScripts(errorDigest || '')) - ); - } - - if (errorMessage || errorComponentStack) { - writeChunk(destination, clientRenderErrorScriptArgInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSStringsForInstructionScripts(errorMessage || '')) - ); - } - - if (errorComponentStack) { - writeChunk(destination, clientRenderErrorScriptArgInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSStringsForInstructionScripts(errorComponentStack)) - ); - } - - return writeChunkAndReturn(destination, clientRenderScript2); -} -const regexForJSStringsInInstructionScripts = /[<\u2028\u2029]/g; - -function escapeJSStringsForInstructionScripts(input) { - const escaped = JSON.stringify(input); - return escaped.replace(regexForJSStringsInInstructionScripts, (match) => { - switch (match) { - // santizing breaking out of strings and script tags - case '<': - return '\\u003c'; - - case '\u2028': - return '\\u2028'; - - case '\u2029': - return '\\u2029'; - - default: { - // eslint-disable-next-line react-internal/prod-error-codes - throw new Error( - 'escapeJSStringsForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React' - ); - } - } - }); -} - -const regexForJSStringsInScripts = /[&><\u2028\u2029]/g; - -function escapeJSObjectForInstructionScripts(input) { - const escaped = JSON.stringify(input); - return escaped.replace(regexForJSStringsInScripts, (match) => { - switch (match) { - // santizing breaking out of strings and script tags - case '&': - return '\\u0026'; - - case '>': - return '\\u003e'; - - case '<': - return '\\u003c'; - - case '\u2028': - return '\\u2028'; - - case '\u2029': - return '\\u2029'; - - default: { - // eslint-disable-next-line react-internal/prod-error-codes - throw new Error( - 'escapeJSObjectForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React' - ); - } - } - }); -} - -const precedencePlaceholderStart = stringToPrecomputedChunk( - '<style data-precedence="' -); -const precedencePlaceholderEnd = stringToPrecomputedChunk('"></style>'); -function writeInitialResources(destination, resources, responseState) { - function flushLinkResource(resource) { - if (!resource.flushed) { - pushLinkImpl(target, resource.props, responseState); - resource.flushed = true; - } - } - - const target = []; - const charset = resources.charset, - bases = resources.bases, - preconnects = resources.preconnects, - fontPreloads = resources.fontPreloads, - precedences = resources.precedences, - usedStylePreloads = resources.usedStylePreloads, - scripts = resources.scripts, - usedScriptPreloads = resources.usedScriptPreloads, - explicitStylePreloads = resources.explicitStylePreloads, - explicitScriptPreloads = resources.explicitScriptPreloads, - headResources = resources.headResources; - - if (charset) { - pushSelfClosing(target, charset.props, 'meta', responseState); - charset.flushed = true; - resources.charset = null; - } - - bases.forEach((r) => { - pushSelfClosing(target, r.props, 'base', responseState); - r.flushed = true; - }); - bases.clear(); - preconnects.forEach((r) => { - // font preload Resources should not already be flushed so we elide this check - pushLinkImpl(target, r.props, responseState); - r.flushed = true; - }); - preconnects.clear(); - fontPreloads.forEach((r) => { - // font preload Resources should not already be flushed so we elide this check - pushLinkImpl(target, r.props, responseState); - r.flushed = true; - }); - fontPreloads.clear(); // Flush stylesheets first by earliest precedence - - precedences.forEach((p, precedence) => { - if (p.size) { - p.forEach((r) => { - // resources should not already be flushed so we elide this check - pushLinkImpl(target, r.props, responseState); - r.flushed = true; - r.inShell = true; - r.hint.flushed = true; - }); - p.clear(); - } else { - target.push( - precedencePlaceholderStart, - stringToChunk(escapeTextForBrowser(precedence)), - precedencePlaceholderEnd - ); - } - }); - usedStylePreloads.forEach(flushLinkResource); - usedStylePreloads.clear(); - scripts.forEach((r) => { - // should never be flushed already - pushScriptImpl(target, r.props, responseState); - r.flushed = true; - r.hint.flushed = true; - }); - scripts.clear(); - usedScriptPreloads.forEach(flushLinkResource); - usedScriptPreloads.clear(); - explicitStylePreloads.forEach(flushLinkResource); - explicitStylePreloads.clear(); - explicitScriptPreloads.forEach(flushLinkResource); - explicitScriptPreloads.clear(); - headResources.forEach((r) => { - switch (r.type) { - case 'title': { - pushTitleImpl(target, r.props, responseState); - break; - } - - case 'meta': { - pushSelfClosing(target, r.props, 'meta', responseState); - break; - } - - case 'link': { - pushLinkImpl(target, r.props, responseState); - break; - } - } - - r.flushed = true; - }); - headResources.clear(); - let i; - let r = true; - - for (i = 0; i < target.length - 1; i++) { - writeChunk(destination, target[i]); - } - - if (i < target.length) { - r = writeChunkAndReturn(destination, target[i]); - } - - return r; -} -function writeImmediateResources(destination, resources, responseState) { - function flushLinkResource(resource) { - if (!resource.flushed) { - pushLinkImpl(target, resource.props, responseState); - resource.flushed = true; - } - } - - const target = []; - const charset = resources.charset, - preconnects = resources.preconnects, - fontPreloads = resources.fontPreloads, - usedStylePreloads = resources.usedStylePreloads, - scripts = resources.scripts, - usedScriptPreloads = resources.usedScriptPreloads, - explicitStylePreloads = resources.explicitStylePreloads, - explicitScriptPreloads = resources.explicitScriptPreloads, - headResources = resources.headResources; - - if (charset) { - pushSelfClosing(target, charset.props, 'meta', responseState); - charset.flushed = true; - resources.charset = null; - } - - preconnects.forEach((r) => { - // font preload Resources should not already be flushed so we elide this check - pushLinkImpl(target, r.props, responseState); - r.flushed = true; - }); - preconnects.clear(); - fontPreloads.forEach((r) => { - // font preload Resources should not already be flushed so we elide this check - pushLinkImpl(target, r.props, responseState); - r.flushed = true; - }); - fontPreloads.clear(); - usedStylePreloads.forEach(flushLinkResource); - usedStylePreloads.clear(); - scripts.forEach((r) => { - // should never be flushed already - pushStartGenericElement(target, r.props, 'script', responseState); - pushEndInstance(target, target, 'script', r.props); - r.flushed = true; - r.hint.flushed = true; - }); - scripts.clear(); - usedScriptPreloads.forEach(flushLinkResource); - usedScriptPreloads.clear(); - explicitStylePreloads.forEach(flushLinkResource); - explicitStylePreloads.clear(); - explicitScriptPreloads.forEach(flushLinkResource); - explicitScriptPreloads.clear(); - headResources.forEach((r) => { - switch (r.type) { - case 'title': { - pushTitleImpl(target, r.props, responseState); - break; - } - - case 'meta': { - pushSelfClosing(target, r.props, 'meta', responseState); - break; - } - - case 'link': { - pushLinkImpl(target, r.props, responseState); - break; - } - } - - r.flushed = true; - }); - headResources.clear(); - let i; - let r = true; - - for (i = 0; i < target.length - 1; i++) { - writeChunk(destination, target[i]); - } - - if (i < target.length) { - r = writeChunkAndReturn(destination, target[i]); - } - - return r; -} - -function hasStyleResourceDependencies(boundaryResources) { - const iter = boundaryResources.values(); // At the moment boundaries only accumulate style resources - // so we assume the type is correct and don't check it - - while (true) { - const _iter$next = iter.next(), - resource = _iter$next.value; - - if (!resource) break; // If every style Resource flushed in the shell we do not need to send - // any dependencies - - if (!resource.inShell) { - return true; - } - } - - return false; -} - -const arrayFirstOpenBracket = stringToPrecomputedChunk('['); -const arraySubsequentOpenBracket = stringToPrecomputedChunk(',['); -const arrayInterstitial = stringToPrecomputedChunk(','); -const arrayCloseBracket = stringToPrecomputedChunk(']'); - -function writeStyleResourceDependencies(destination, boundaryResources) { - writeChunk(destination, arrayFirstOpenBracket); - let nextArrayOpenBrackChunk = arrayFirstOpenBracket; - boundaryResources.forEach((resource) => { - if (resource.inShell); - else if (resource.flushed) { - writeChunk(destination, nextArrayOpenBrackChunk); - writeStyleResourceDependencyHrefOnly(destination, resource.href); - writeChunk(destination, arrayCloseBracket); - nextArrayOpenBrackChunk = arraySubsequentOpenBracket; - } else { - writeChunk(destination, nextArrayOpenBrackChunk); - writeStyleResourceDependency( - destination, - resource.href, - resource.precedence, - resource.props - ); - writeChunk(destination, arrayCloseBracket); - nextArrayOpenBrackChunk = arraySubsequentOpenBracket; - resource.flushed = true; - resource.hint.flushed = true; - } - }); - writeChunk(destination, arrayCloseBracket); -} - -function writeStyleResourceDependencyHrefOnly(destination, href) { - const coercedHref = '' + href; - writeChunk( - destination, - stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)) - ); -} - -function writeStyleResourceDependency(destination, href, precedence, props) { - const coercedHref = '' + href; - writeChunk( - destination, - stringToChunk(escapeJSObjectForInstructionScripts(coercedHref)) - ); - - const coercedPrecedence = '' + precedence; - writeChunk(destination, arrayInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSObjectForInstructionScripts(coercedPrecedence)) - ); - - for (const propKey in props) { - if (hasOwnProperty.call(props, propKey)) { - const propValue = props[propKey]; - - if (propValue == null) { - continue; - } - - switch (propKey) { - case 'href': - case 'rel': - case 'precedence': - case 'data-precedence': { - break; - } - - case 'children': - case 'dangerouslySetInnerHTML': - throw new Error( - 'link' + - ' is a self-closing tag and must neither have `children` nor ' + - 'use `dangerouslySetInnerHTML`.' - ); - // eslint-disable-next-line-no-fallthrough - - default: - writeStyleResourceAttribute(destination, propKey, propValue); - break; - } - } - } - - return null; -} - -function writeStyleResourceAttribute(destination, name, value) { - let attributeName = name.toLowerCase(); - let attributeValue; - - switch (typeof value) { - case 'function': - case 'symbol': - return; - } - - switch (name) { - // Reserved names - case 'innerHTML': - case 'dangerouslySetInnerHTML': - case 'suppressContentEditableWarning': - case 'suppressHydrationWarning': - case 'style': - // Ignored - return; - // Attribute renames - - case 'className': - attributeName = 'class'; - break; - // Booleans - - case 'hidden': - if (value === false) { - return; - } - - attributeValue = ''; - break; - // Santized URLs - - case 'src': - case 'href': { - attributeValue = '' + value; - break; - } - - default: { - if (!isAttributeNameSafe(name)) { - return; - } - } - } - - if ( - // shouldIgnoreAttribute - // We have already filtered out null/undefined and reserved words. - name.length > 2 && - (name[0] === 'o' || name[0] === 'O') && - (name[1] === 'n' || name[1] === 'N') - ) { - return; - } - - attributeValue = '' + value; - writeChunk(destination, arrayInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSObjectForInstructionScripts(attributeName)) - ); - writeChunk(destination, arrayInterstitial); - writeChunk( - destination, - stringToChunk(escapeJSObjectForInstructionScripts(attributeValue)) - ); -} - -// ATTENTION -// When adding new symbols to this file, -// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols' -// The Symbol used to tag the ReactElement-like types. -const REACT_ELEMENT_TYPE = Symbol.for('react.element'); -const REACT_PORTAL_TYPE = Symbol.for('react.portal'); -const REACT_FRAGMENT_TYPE = Symbol.for('react.fragment'); -const REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode'); -const REACT_PROFILER_TYPE = Symbol.for('react.profiler'); -const REACT_PROVIDER_TYPE = Symbol.for('react.provider'); -const REACT_CONTEXT_TYPE = Symbol.for('react.context'); -const REACT_SERVER_CONTEXT_TYPE = Symbol.for('react.server_context'); -const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref'); -const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense'); -const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list'); -const REACT_MEMO_TYPE = Symbol.for('react.memo'); -const REACT_LAZY_TYPE = Symbol.for('react.lazy'); -const REACT_SCOPE_TYPE = Symbol.for('react.scope'); -const REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for('react.debug_trace_mode'); -const REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen'); -const REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden'); -const REACT_CACHE_TYPE = Symbol.for('react.cache'); -const REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED = Symbol.for( - 'react.default_value' -); -const REACT_MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel'); -const MAYBE_ITERATOR_SYMBOL = Symbol.iterator; -const FAUX_ITERATOR_SYMBOL = '@@iterator'; -function getIteratorFn(maybeIterable) { - if (maybeIterable === null || typeof maybeIterable !== 'object') { - return null; - } - - const maybeIterator = - (MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL]) || - maybeIterable[FAUX_ITERATOR_SYMBOL]; - - if (typeof maybeIterator === 'function') { - return maybeIterator; - } - - return null; -} - -function getWrappedName(outerType, innerType, wrapperName) { - const displayName = outerType.displayName; - - if (displayName) { - return displayName; - } - - const functionName = innerType.displayName || innerType.name || ''; - return functionName !== '' - ? wrapperName + '(' + functionName + ')' - : wrapperName; -} // Keep in sync with react-reconciler/getComponentNameFromFiber - -function getContextName(type) { - return type.displayName || 'Context'; -} // Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead. - -function getComponentNameFromType(type) { - if (type == null) { - // Host root, text node or just invalid type. - return null; - } - - if (typeof type === 'function') { - return type.displayName || type.name || null; - } - - if (typeof type === 'string') { - return type; - } - - switch (type) { - case REACT_FRAGMENT_TYPE: - return 'Fragment'; - - case REACT_PORTAL_TYPE: - return 'Portal'; - - case REACT_PROFILER_TYPE: - return 'Profiler'; - - case REACT_STRICT_MODE_TYPE: - return 'StrictMode'; - - case REACT_SUSPENSE_TYPE: - return 'Suspense'; - - case REACT_SUSPENSE_LIST_TYPE: - return 'SuspenseList'; - - case REACT_CACHE_TYPE: { - return 'Cache'; - } - } - - if (typeof type === 'object') { - switch (type.$$typeof) { - case REACT_CONTEXT_TYPE: - const context = type; - return getContextName(context) + '.Consumer'; - - case REACT_PROVIDER_TYPE: - const provider = type; - return getContextName(provider._context) + '.Provider'; - - case REACT_FORWARD_REF_TYPE: - return getWrappedName(type, type.render, 'ForwardRef'); - - case REACT_MEMO_TYPE: - const outerName = type.displayName || null; - - if (outerName !== null) { - return outerName; - } - - return getComponentNameFromType(type.type) || 'Memo'; - - case REACT_LAZY_TYPE: { - const lazyComponent = type; - const payload = lazyComponent._payload; - const init = lazyComponent._init; - - try { - return getComponentNameFromType(init(payload)); - } catch (x) { - return null; - } - } - - case REACT_SERVER_CONTEXT_TYPE: { - const context2 = type; - return (context2.displayName || context2._globalName) + '.Provider'; - } - - // eslint-disable-next-line no-fallthrough - } - } - - return null; -} - -const ReactSharedInternals = - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED$1; - -const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; - -const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; - -const emptyContextObject = {}; - -function getMaskedContext(type, unmaskedContext) { - { - const contextTypes = type.contextTypes; - - if (!contextTypes) { - return emptyContextObject; - } - - const context = {}; - - for (const key in contextTypes) { - context[key] = unmaskedContext[key]; - } - - return context; - } -} -function processChildContext(instance, type, parentContext, childContextTypes) { - { - // TODO (bvaughn) Replace this behavior with an invariant() in the future. - // It has only been added in Fiber to match the (unintentional) behavior in Stack. - if (typeof instance.getChildContext !== 'function') { - return parentContext; - } - - const childContext = instance.getChildContext(); - - for (const contextKey in childContext) { - if (!(contextKey in childContextTypes)) { - throw new Error( - (getComponentNameFromType(type) || 'Unknown') + - '.getChildContext(): key "' + - contextKey + - '" is not defined in childContextTypes.' - ); - } - } - - return assign({}, parentContext, childContext); - } -} - -// Forming a reverse tree. - -const rootContextSnapshot = null; // We assume that this runtime owns the "current" field on all ReactContext instances. -// This global (actually thread local) state represents what state all those "current", -// fields are currently in. - -let currentActiveSnapshot = null; - -function popNode(prev) { - { - prev.context._currentValue = prev.parentValue; - } -} - -function pushNode(next) { - { - next.context._currentValue = next.value; - } -} - -function popToNearestCommonAncestor(prev, next) { - if (prev === next); - else { - popNode(prev); - const parentPrev = prev.parent; - const parentNext = next.parent; - - if (parentPrev === null) { - if (parentNext !== null) { - throw new Error( - 'The stacks must reach the root at the same time. This is a bug in React.' - ); - } - } else { - if (parentNext === null) { - throw new Error( - 'The stacks must reach the root at the same time. This is a bug in React.' - ); - } - - popToNearestCommonAncestor(parentPrev, parentNext); - } // On the way back, we push the new ones that weren't common. - - pushNode(next); - } -} - -function popAllPrevious(prev) { - popNode(prev); - const parentPrev = prev.parent; - - if (parentPrev !== null) { - popAllPrevious(parentPrev); - } -} - -function pushAllNext(next) { - const parentNext = next.parent; - - if (parentNext !== null) { - pushAllNext(parentNext); - } - - pushNode(next); -} - -function popPreviousToCommonLevel(prev, next) { - popNode(prev); - const parentPrev = prev.parent; - - if (parentPrev === null) { - throw new Error( - 'The depth must equal at least at zero before reaching the root. This is a bug in React.' - ); - } - - if (parentPrev.depth === next.depth) { - // We found the same level. Now we just need to find a shared ancestor. - popToNearestCommonAncestor(parentPrev, next); - } else { - // We must still be deeper. - popPreviousToCommonLevel(parentPrev, next); - } -} - -function popNextToCommonLevel(prev, next) { - const parentNext = next.parent; - - if (parentNext === null) { - throw new Error( - 'The depth must equal at least at zero before reaching the root. This is a bug in React.' - ); - } - - if (prev.depth === parentNext.depth) { - // We found the same level. Now we just need to find a shared ancestor. - popToNearestCommonAncestor(prev, parentNext); - } else { - // We must still be deeper. - popNextToCommonLevel(prev, parentNext); - } - - pushNode(next); -} // Perform context switching to the new snapshot. -// To make it cheap to read many contexts, while not suspending, we make the switch eagerly by -// updating all the context's current values. That way reads, always just read the current value. -// At the cost of updating contexts even if they're never read by this subtree. - -function switchContext(newSnapshot) { - // The basic algorithm we need to do is to pop back any contexts that are no longer on the stack. - // We also need to update any new contexts that are now on the stack with the deepest value. - // The easiest way to update new contexts is to just reapply them in reverse order from the - // perspective of the backpointers. To avoid allocating a lot when switching, we use the stack - // for that. Therefore this algorithm is recursive. - // 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go. - // 2) Then we find the nearest common ancestor from there. Popping old contexts as we go. - // 3) Then we reapply new contexts on the way back up the stack. - const prev = currentActiveSnapshot; - const next = newSnapshot; - - if (prev !== next) { - if (prev === null) { - // $FlowFixMe: This has to be non-null since it's not equal to prev. - pushAllNext(next); - } else if (next === null) { - popAllPrevious(prev); - } else if (prev.depth === next.depth) { - popToNearestCommonAncestor(prev, next); - } else if (prev.depth > next.depth) { - popPreviousToCommonLevel(prev, next); - } else { - popNextToCommonLevel(prev, next); - } - - currentActiveSnapshot = next; - } -} -function pushProvider(context, nextValue) { - let prevValue; - - { - prevValue = context._currentValue; - context._currentValue = nextValue; - } - - const prevNode = currentActiveSnapshot; - const newNode = { - parent: prevNode, - depth: prevNode === null ? 0 : prevNode.depth + 1, - context: context, - parentValue: prevValue, - value: nextValue, - }; - currentActiveSnapshot = newNode; - return newNode; -} -function popProvider(context) { - const prevSnapshot = currentActiveSnapshot; - - if (prevSnapshot === null) { - throw new Error( - 'Tried to pop a Context at the root of the app. This is a bug in React.' - ); - } - - { - const value = prevSnapshot.parentValue; - - if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) { - prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue; - } else { - prevSnapshot.context._currentValue = value; - } - } - - return (currentActiveSnapshot = prevSnapshot.parent); -} -function getActiveContext() { - return currentActiveSnapshot; -} -function readContext(context) { - const value = context._currentValue; - return value; -} - -/** - * `ReactInstanceMap` maintains a mapping from a public facing stateful - * instance (key) and the internal representation (value). This allows public - * methods to accept the user facing instance as an argument and map them back - * to internal methods. - * - * Note that this module is currently shared and assumed to be stateless. - * If this becomes an actual Map, that will break. - */ -function get(key) { - return key._reactInternals; -} -function set(key, value) { - key._reactInternals = value; -} - -const classComponentUpdater = { - isMounted(inst) { - return false; - }, - - enqueueSetState(inst, payload, callback) { - const internals = get(inst); - - if (internals.queue === null); - else { - internals.queue.push(payload); - } - }, - - enqueueReplaceState(inst, payload, callback) { - const internals = get(inst); - internals.replace = true; - internals.queue = [payload]; - }, - - enqueueForceUpdate(inst, callback) { - const internals = get(inst); - - if (internals.queue === null); - }, -}; - -function applyDerivedStateFromProps( - instance, - ctor, - getDerivedStateFromProps, - prevState, - nextProps -) { - const partialState = getDerivedStateFromProps(nextProps, prevState); - - const newState = - partialState === null || partialState === undefined - ? prevState - : assign({}, prevState, partialState); - return newState; -} - -function constructClassInstance(ctor, props, maskedLegacyContext) { - let context = emptyContextObject; - const contextType = ctor.contextType; - - if (typeof contextType === 'object' && contextType !== null) { - context = readContext(contextType); - } else { - context = maskedLegacyContext; - } - - const instance = new ctor(props, context); - - return instance; -} - -function callComponentWillMount(type, instance) { - const oldState = instance.state; - - if (typeof instance.componentWillMount === 'function') { - instance.componentWillMount(); - } - - if (typeof instance.UNSAFE_componentWillMount === 'function') { - instance.UNSAFE_componentWillMount(); - } - - if (oldState !== instance.state) { - classComponentUpdater.enqueueReplaceState(instance, instance.state, null); - } -} - -function processUpdateQueue( - internalInstance, - inst, - props, - maskedLegacyContext -) { - if (internalInstance.queue !== null && internalInstance.queue.length > 0) { - const oldQueue = internalInstance.queue; - const oldReplace = internalInstance.replace; - internalInstance.queue = null; - internalInstance.replace = false; - - if (oldReplace && oldQueue.length === 1) { - inst.state = oldQueue[0]; - } else { - let nextState = oldReplace ? oldQueue[0] : inst.state; - let dontMutate = true; - - for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) { - const partial = oldQueue[i]; - const partialState = - typeof partial === 'function' - ? partial.call(inst, nextState, props, maskedLegacyContext) - : partial; - - if (partialState != null) { - if (dontMutate) { - dontMutate = false; - nextState = assign({}, nextState, partialState); - } else { - assign(nextState, partialState); - } - } - } - - inst.state = nextState; - } - } else { - internalInstance.queue = null; - } -} // Invokes the mount life-cycles on a previously never rendered instance. - -function mountClassInstance(instance, ctor, newProps, maskedLegacyContext) { - const initialState = instance.state !== undefined ? instance.state : null; - instance.updater = classComponentUpdater; - instance.props = newProps; - instance.state = initialState; // We don't bother initializing the refs object on the server, since we're not going to resolve them anyway. - // The internal instance will be used to manage updates that happen during this mount. - - const internalInstance = { - queue: [], - replace: false, - }; - set(instance, internalInstance); - const contextType = ctor.contextType; - - if (typeof contextType === 'object' && contextType !== null) { - instance.context = readContext(contextType); - } else { - instance.context = maskedLegacyContext; - } - - const getDerivedStateFromProps = ctor.getDerivedStateFromProps; - - if (typeof getDerivedStateFromProps === 'function') { - instance.state = applyDerivedStateFromProps( - instance, - ctor, - getDerivedStateFromProps, - initialState, - newProps - ); - } // In order to support react-lifecycles-compat polyfilled components, - // Unsafe lifecycles should not be invoked for components using the new APIs. - - if ( - typeof ctor.getDerivedStateFromProps !== 'function' && - typeof instance.getSnapshotBeforeUpdate !== 'function' && - (typeof instance.UNSAFE_componentWillMount === 'function' || - typeof instance.componentWillMount === 'function') - ) { - callComponentWillMount(ctor, instance); // If we had additional state updates during this life-cycle, let's - // process them now. - - processUpdateQueue( - internalInstance, - instance, - newProps, - maskedLegacyContext - ); - } -} - -// Ids are base 32 strings whose binary representation corresponds to the -// position of a node in a tree. -// Every time the tree forks into multiple children, we add additional bits to -// the left of the sequence that represent the position of the child within the -// current level of children. -// -// 00101 00010001011010101 -// ╰─┬─╯ ╰───────┬───────╯ -// Fork 5 of 20 Parent id -// -// The leading 0s are important. In the above example, you only need 3 bits to -// represent slot 5. However, you need 5 bits to represent all the forks at -// the current level, so we must account for the empty bits at the end. -// -// For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, -// the zeroth id at a level would be indistinguishable from its parent. -// -// If a node has only one child, and does not materialize an id (i.e. does not -// contain a useId hook), then we don't need to allocate any space in the -// sequence. It's treated as a transparent indirection. For example, these two -// trees produce the same ids: -// -// <> <> -// <Indirection> <A /> -// <A /> <B /> -// </Indirection> </> -// <B /> -// </> -// -// However, we cannot skip any node that materializes an id. Otherwise, a parent -// id that does not fork would be indistinguishable from its child id. For -// example, this tree does not fork, but the parent and child must have -// different ids. -// -// <Parent> -// <Child /> -// </Parent> -// -// To handle this scenario, every time we materialize an id, we allocate a -// new level with a single slot. You can think of this as a fork with only one -// prong, or an array of children with length 1. -// -// It's possible for the size of the sequence to exceed 32 bits, the max -// size for bitwise operations. When this happens, we make more room by -// converting the right part of the id to a string and storing it in an overflow -// variable. We use a base 32 string representation, because 32 is the largest -// power of 2 that is supported by toString(). We want the base to be large so -// that the resulting ids are compact, and we want the base to be a power of 2 -// because every log2(base) bits corresponds to a single character, i.e. every -// log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without -// affecting the final result. -const emptyTreeContext = { - id: 1, - overflow: '', -}; -function getTreeId(context) { - const overflow = context.overflow; - const idWithLeadingBit = context.id; - const id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit); - return id.toString(32) + overflow; -} -function pushTreeContext(baseContext, totalChildren, index) { - const baseIdWithLeadingBit = baseContext.id; - const baseOverflow = baseContext.overflow; // The leftmost 1 marks the end of the sequence, non-inclusive. It's not part - // of the id; we use it to account for leading 0s. - - const baseLength = getBitLength(baseIdWithLeadingBit) - 1; - const baseId = baseIdWithLeadingBit & ~(1 << baseLength); - const slot = index + 1; - const length = getBitLength(totalChildren) + baseLength; // 30 is the max length we can store without overflowing, taking into - // consideration the leading 1 we use to mark the end of the sequence. - - if (length > 30) { - // We overflowed the bitwise-safe range. Fall back to slower algorithm. - // This branch assumes the length of the base id is greater than 5; it won't - // work for smaller ids, because you need 5 bits per character. - // - // We encode the id in multiple steps: first the base id, then the - // remaining digits. - // - // Each 5 bit sequence corresponds to a single base 32 character. So for - // example, if the current id is 23 bits long, we can convert 20 of those - // bits into a string of 4 characters, with 3 bits left over. - // - // First calculate how many bits in the base id represent a complete - // sequence of characters. - const numberOfOverflowBits = baseLength - (baseLength % 5); // Then create a bitmask that selects only those bits. - - const newOverflowBits = (1 << numberOfOverflowBits) - 1; // Select the bits, and convert them to a base 32 string. - - const newOverflow = (baseId & newOverflowBits).toString(32); // Now we can remove those bits from the base id. - - const restOfBaseId = baseId >> numberOfOverflowBits; - const restOfBaseLength = baseLength - numberOfOverflowBits; // Finally, encode the rest of the bits using the normal algorithm. Because - // we made more room, this time it won't overflow. - - const restOfLength = getBitLength(totalChildren) + restOfBaseLength; - const restOfNewBits = slot << restOfBaseLength; - const id = restOfNewBits | restOfBaseId; - const overflow = newOverflow + baseOverflow; - return { - id: (1 << restOfLength) | id, - overflow, - }; - } else { - // Normal path - const newBits = slot << baseLength; - const id = newBits | baseId; - const overflow = baseOverflow; - return { - id: (1 << length) | id, - overflow, - }; - } -} - -function getBitLength(number) { - return 32 - clz32(number); -} - -function getLeadingBit(id) { - return 1 << (getBitLength(id) - 1); -} // TODO: Math.clz32 is supported in Node 12+. Maybe we can drop the fallback. - -const clz32 = Math.clz32 ? Math.clz32 : clz32Fallback; // Count leading zeros. -// Based on: -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 - -const log = Math.log; -const LN2 = Math.LN2; - -function clz32Fallback(x) { - const asUint = x >>> 0; - - if (asUint === 0) { - return 32; - } - - return (31 - ((log(asUint) / LN2) | 0)) | 0; -} - -// Corresponds to ReactFiberWakeable and ReactFlightWakeable modules. Generally, -// changes to one module should be reflected in the others. -// TODO: Rename this module and the corresponding Fiber one to "Thenable" -// instead of "Wakeable". Or some other more appropriate name. -function createThenableState() { - // The ThenableState is created the first time a component suspends. If it - // suspends again, we'll reuse the same state. - return []; -} - -function noop() {} - -function trackUsedThenable(thenableState, thenable, index) { - const previous = thenableState[index]; - - if (previous === undefined) { - thenableState.push(thenable); - } else { - if (previous !== thenable) { - // Reuse the previous thenable, and drop the new one. We can assume - // they represent the same value, because components are idempotent. - // Avoid an unhandled rejection errors for the Promises that we'll - // intentionally ignore. - thenable.then(noop, noop); - thenable = previous; - } - } // We use an expando to track the status and result of a thenable so that we - // can synchronously unwrap the value. Think of this as an extension of the - // Promise API, or a custom interface that is a superset of Thenable. - // - // If the thenable doesn't have a status, set it to "pending" and attach - // a listener that will update its status and result when it resolves. - - switch (thenable.status) { - case 'fulfilled': { - const fulfilledValue = thenable.value; - return fulfilledValue; - } - - case 'rejected': { - const rejectedError = thenable.reason; - throw rejectedError; - } - - default: { - if (typeof thenable.status === 'string'); - else { - const pendingThenable = thenable; - pendingThenable.status = 'pending'; - pendingThenable.then( - (fulfilledValue) => { - if (thenable.status === 'pending') { - const fulfilledThenable = thenable; - fulfilledThenable.status = 'fulfilled'; - fulfilledThenable.value = fulfilledValue; - } - }, - (error) => { - if (thenable.status === 'pending') { - const rejectedThenable = thenable; - rejectedThenable.status = 'rejected'; - rejectedThenable.reason = error; - } - } - ); - } // Suspend. - // TODO: Throwing here is an implementation detail that allows us to - // unwind the call stack. But we shouldn't allow it to leak into - // userspace. Throw an opaque placeholder value instead of the - // actual thenable. If it doesn't get captured by the work loop, log - // a warning, because that means something in userspace must have - // caught it. - - throw thenable; - } - } -} - -/** - * inlined Object.is polyfill to avoid requiring consumers ship their own - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is - */ -function is(x, y) { - return ( - (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare - ); -} - -const objectIs = typeof Object.is === 'function' ? Object.is : is; // $FlowFixMe[method-unbinding] - -let currentlyRenderingComponent = null; -let currentlyRenderingTask = null; -let firstWorkInProgressHook = null; -let workInProgressHook = null; // Whether the work-in-progress hook is a re-rendered hook - -let isReRender = false; // Whether an update was scheduled during the currently executing render pass. - -let didScheduleRenderPhaseUpdate = false; // Counts the number of useId hooks in this component - -let localIdCounter = 0; // Counts the number of use(thenable) calls in this component - -let thenableIndexCounter = 0; -let thenableState = null; // Lazily created map of render-phase updates - -let renderPhaseUpdates = null; // Counter to prevent infinite loops. - -let numberOfReRenders = 0; -const RE_RENDER_LIMIT = 25; - -function resolveCurrentlyRenderingComponent() { - if (currentlyRenderingComponent === null) { - throw new Error( - 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + - ' one of the following reasons:\n' + - '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + - '2. You might be breaking the Rules of Hooks\n' + - '3. You might have more than one copy of React in the same app\n' + - 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.' - ); - } - - return currentlyRenderingComponent; -} - -function areHookInputsEqual(nextDeps, prevDeps) { - if (prevDeps === null) { - return false; - } - - for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { - // $FlowFixMe[incompatible-use] found when upgrading Flow - if (objectIs(nextDeps[i], prevDeps[i])) { - continue; - } - - return false; - } - - return true; -} - -function createHook() { - if (numberOfReRenders > 0) { - throw new Error('Rendered more hooks than during the previous render'); - } - - return { - memoizedState: null, - queue: null, - next: null, - }; -} - -function createWorkInProgressHook() { - if (workInProgressHook === null) { - // This is the first hook in the list - if (firstWorkInProgressHook === null) { - isReRender = false; - firstWorkInProgressHook = workInProgressHook = createHook(); - } else { - // There's already a work-in-progress. Reuse it. - isReRender = true; - workInProgressHook = firstWorkInProgressHook; - } - } else { - if (workInProgressHook.next === null) { - isReRender = false; // Append to the end of the list - - workInProgressHook = workInProgressHook.next = createHook(); - } else { - // There's already a work-in-progress. Reuse it. - isReRender = true; - workInProgressHook = workInProgressHook.next; - } - } - - return workInProgressHook; -} - -function prepareToUseHooks(task, componentIdentity, prevThenableState) { - currentlyRenderingComponent = componentIdentity; - currentlyRenderingTask = task; - // didScheduleRenderPhaseUpdate = false; - // firstWorkInProgressHook = null; - // numberOfReRenders = 0; - // renderPhaseUpdates = null; - // workInProgressHook = null; - - localIdCounter = 0; - thenableIndexCounter = 0; - thenableState = prevThenableState; -} -function finishHooks(Component, props, children, refOrContext) { - // This must be called after every function component to prevent hooks from - // being used in classes. - while (didScheduleRenderPhaseUpdate) { - // Updates were scheduled during the render phase. They are stored in - // the `renderPhaseUpdates` map. Call the component again, reusing the - // work-in-progress hooks and applying the additional updates on top. Keep - // restarting until no more updates are scheduled. - didScheduleRenderPhaseUpdate = false; - localIdCounter = 0; - thenableIndexCounter = 0; - numberOfReRenders += 1; // Start over from the beginning of the list - - workInProgressHook = null; - children = Component(props, refOrContext); - } - - resetHooksState(); - return children; -} -function getThenableStateAfterSuspending() { - const state = thenableState; - thenableState = null; - return state; -} -function checkDidRenderIdHook() { - // This should be called immediately after every finishHooks call. - // Conceptually, it's part of the return value of finishHooks; it's only a - // separate function to avoid using an array tuple. - const didRenderIdHook = localIdCounter !== 0; - return didRenderIdHook; -} // Reset the internal hooks state if an error occurs while rendering a component - -function resetHooksState() { - currentlyRenderingComponent = null; - currentlyRenderingTask = null; - didScheduleRenderPhaseUpdate = false; - firstWorkInProgressHook = null; - numberOfReRenders = 0; - renderPhaseUpdates = null; - workInProgressHook = null; -} - -function readContext$1(context) { - return readContext(context); -} - -function useContext(context) { - resolveCurrentlyRenderingComponent(); - return readContext(context); -} - -function basicStateReducer(state, action) { - // $FlowFixMe: Flow doesn't like mixed types - return typeof action === 'function' ? action(state) : action; -} - -function useState(initialState) { - return useReducer( - basicStateReducer, // useReducer has a special case to support lazy useState initializers - initialState - ); -} -function useReducer(reducer, initialArg, init) { - currentlyRenderingComponent = resolveCurrentlyRenderingComponent(); - workInProgressHook = createWorkInProgressHook(); - - if (isReRender) { - // This is a re-render. Apply the new render phase updates to the previous - // current hook. - const queue = workInProgressHook.queue; - const dispatch = queue.dispatch; - - if (renderPhaseUpdates !== null) { - // Render phase updates are stored in a map of queue -> linked list - const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); - - if (firstRenderPhaseUpdate !== undefined) { - // $FlowFixMe[incompatible-use] found when upgrading Flow - renderPhaseUpdates.delete(queue); // $FlowFixMe[incompatible-use] found when upgrading Flow - - let newState = workInProgressHook.memoizedState; - let update = firstRenderPhaseUpdate; - - do { - // Process this render phase update. We don't have to check the - // priority because it will always be the same as the current - // render's. - const action = update.action; - - newState = reducer(newState, action); - - update = update.next; - } while (update !== null); // $FlowFixMe[incompatible-use] found when upgrading Flow - - workInProgressHook.memoizedState = newState; - return [newState, dispatch]; - } - } // $FlowFixMe[incompatible-use] found when upgrading Flow - - return [workInProgressHook.memoizedState, dispatch]; - } else { - let initialState; - - if (reducer === basicStateReducer) { - // Special case for `useState`. - initialState = - typeof initialArg === 'function' ? initialArg() : initialArg; - } else { - initialState = init !== undefined ? init(initialArg) : initialArg; - } - - workInProgressHook.memoizedState = initialState; // $FlowFixMe[incompatible-use] found when upgrading Flow - - const queue = (workInProgressHook.queue = { - last: null, - dispatch: null, - }); - const dispatch = (queue.dispatch = dispatchAction.bind( - null, - currentlyRenderingComponent, - queue - )); // $FlowFixMe[incompatible-use] found when upgrading Flow - - return [workInProgressHook.memoizedState, dispatch]; - } -} - -function useMemo(nextCreate, deps) { - currentlyRenderingComponent = resolveCurrentlyRenderingComponent(); - workInProgressHook = createWorkInProgressHook(); - const nextDeps = deps === undefined ? null : deps; - - if (workInProgressHook !== null) { - const prevState = workInProgressHook.memoizedState; - - if (prevState !== null) { - if (nextDeps !== null) { - const prevDeps = prevState[1]; - - if (areHookInputsEqual(nextDeps, prevDeps)) { - return prevState[0]; - } - } - } - } - - const nextValue = nextCreate(); - - workInProgressHook.memoizedState = [nextValue, nextDeps]; - return nextValue; -} - -function useRef(initialValue) { - currentlyRenderingComponent = resolveCurrentlyRenderingComponent(); - workInProgressHook = createWorkInProgressHook(); - const previousRef = workInProgressHook.memoizedState; - - if (previousRef === null) { - const ref = { - current: initialValue, - }; - - workInProgressHook.memoizedState = ref; - return ref; - } else { - return previousRef; - } -} - -function useLayoutEffect(create, inputs) {} - -function dispatchAction(componentIdentity, queue, action) { - if (numberOfReRenders >= RE_RENDER_LIMIT) { - throw new Error( - 'Too many re-renders. React limits the number of renders to prevent ' + - 'an infinite loop.' - ); - } - - if (componentIdentity === currentlyRenderingComponent) { - // This is a render phase update. Stash it in a lazily-created map of - // queue -> linked list of updates. After this render pass, we'll restart - // and apply the stashed updates on top of the work-in-progress hook. - didScheduleRenderPhaseUpdate = true; - const update = { - action, - next: null, - }; - - if (renderPhaseUpdates === null) { - renderPhaseUpdates = new Map(); - } - - const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); - - if (firstRenderPhaseUpdate === undefined) { - // $FlowFixMe[incompatible-use] found when upgrading Flow - renderPhaseUpdates.set(queue, update); - } else { - // Append the update to the end of the list. - let lastRenderPhaseUpdate = firstRenderPhaseUpdate; - - while (lastRenderPhaseUpdate.next !== null) { - lastRenderPhaseUpdate = lastRenderPhaseUpdate.next; - } - - lastRenderPhaseUpdate.next = update; - } - } -} - -function useCallback(callback, deps) { - return useMemo(() => callback, deps); -} - -function throwOnUseEventCall() { - throw new Error( - "A function wrapped in useEvent can't be called during rendering." - ); -} - -function useEvent(callback) { - // $FlowIgnore[incompatible-return] - return throwOnUseEventCall; -} // TODO Decide on how to implement this hook for server rendering. -// If a mutation occurs during render, consider triggering a Suspense boundary -// and falling back to client rendering. - -function useMutableSource(source, getSnapshot, subscribe) { - resolveCurrentlyRenderingComponent(); - return getSnapshot(source._source); -} - -function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) { - if (getServerSnapshot === undefined) { - throw new Error( - 'Missing getServerSnapshot, which is required for ' + - 'server-rendered content. Will revert to client rendering.' - ); - } - - return getServerSnapshot(); -} - -function useDeferredValue(value) { - resolveCurrentlyRenderingComponent(); - return value; -} - -function unsupportedStartTransition() { - throw new Error('startTransition cannot be called during server rendering.'); -} - -function useTransition() { - resolveCurrentlyRenderingComponent(); - return [false, unsupportedStartTransition]; -} - -function useId() { - const task = currentlyRenderingTask; - const treeId = getTreeId(task.treeContext); - const responseState = currentResponseState; - - if (responseState === null) { - throw new Error( - 'Invalid hook call. Hooks can only be called inside of the body of a function component.' - ); - } - - const localId = localIdCounter++; - return makeId(responseState, treeId, localId); -} - -function use(usable) { - if (usable !== null && typeof usable === 'object') { - // $FlowFixMe[method-unbinding] - if (typeof usable.then === 'function') { - // This is a thenable. - const thenable = usable; // Track the position of the thenable within this fiber. - - const index = thenableIndexCounter; - thenableIndexCounter += 1; - - if (thenableState === null) { - thenableState = createThenableState(); - } - - return trackUsedThenable(thenableState, thenable, index); - } else if ( - usable.$$typeof === REACT_CONTEXT_TYPE || - usable.$$typeof === REACT_SERVER_CONTEXT_TYPE - ) { - const context = usable; - return readContext$1(context); - } - } // eslint-disable-next-line react-internal/safe-string-coercion - - throw new Error('An unsupported type was passed to use(): ' + String(usable)); -} - -function unsupportedRefresh() { - throw new Error('Cache cannot be refreshed during server rendering.'); -} - -function useCacheRefresh() { - return unsupportedRefresh; -} - -function useMemoCache(size) { - const data = new Array(size); - - for (let i = 0; i < size; i++) { - data[i] = REACT_MEMO_CACHE_SENTINEL; - } - - return data; -} - -function noop$1() {} - -const HooksDispatcher = { - readContext: readContext$1, - useContext, - useMemo, - useReducer, - useRef, - useState, - useInsertionEffect: noop$1, - useLayoutEffect, - useCallback, - // useImperativeHandle is not run in the server environment - useImperativeHandle: noop$1, - // Effects are not run in the server environment. - useEffect: noop$1, - // Debugging effect - useDebugValue: noop$1, - useDeferredValue, - useTransition, - useId, - // Subscriptions are not setup in a server environment. - useMutableSource, - useSyncExternalStore, -}; - -{ - HooksDispatcher.useCacheRefresh = useCacheRefresh; -} - -{ - HooksDispatcher.useEvent = useEvent; -} - -{ - HooksDispatcher.useMemoCache = useMemoCache; -} - -{ - HooksDispatcher.use = use; -} - -let currentResponseState = null; -function setCurrentResponseState(responseState) { - currentResponseState = responseState; -} - -function getCacheSignal() { - throw new Error('Not implemented.'); -} - -function getCacheForType(resourceType) { - throw new Error('Not implemented.'); -} - -const DefaultCacheDispatcher = { - getCacheSignal, - getCacheForType, -}; - -const ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher; -const ReactCurrentCache = ReactSharedInternals.ReactCurrentCache; -const ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame; -const PENDING = 0; -const COMPLETED = 1; -const FLUSHED = 2; -const ABORTED = 3; -const ERRORED = 4; -const OPEN = 0; -const CLOSING = 1; -const CLOSED = 2; -// This is a default heuristic for how to split up the HTML content into progressive -// loading. Our goal is to be able to display additional new content about every 500ms. -// Faster than that is unnecessary and should be throttled on the client. It also -// adds unnecessary overhead to do more splits. We don't know if it's a higher or lower -// end device but higher end suffer less from the overhead than lower end does from -// not getting small enough pieces. We error on the side of low end. -// We base this on low end 3G speeds which is about 500kbits per second. We assume -// that there can be a reasonable drop off from max bandwidth which leaves you with -// as little as 80%. We can receive half of that each 500ms - at best. In practice, -// a little bandwidth is lost to processing and contention - e.g. CSS and images that -// are downloaded along with the main content. So we estimate about half of that to be -// the lower end throughput. In other words, we expect that you can at least show -// about 12.5kb of content per 500ms. Not counting starting latency for the first -// paint. -// 500 * 1024 / 8 * .8 * 0.5 / 2 -const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800; - -function defaultErrorHandler(error) { - console['error'](error); // Don't transform to our wrapper - - return null; -} - -function noop$2() {} - -function createRequest( - children, - responseState, - rootFormatContext, - progressiveChunkSize, - onError, - onAllReady, - onShellReady, - onShellError, - onFatalError -) { - const pingedTasks = []; - const abortSet = new Set(); - const resources = createResources(); - const request = { - destination: null, - responseState, - progressiveChunkSize: - progressiveChunkSize === undefined - ? DEFAULT_PROGRESSIVE_CHUNK_SIZE - : progressiveChunkSize, - status: OPEN, - fatalError: null, - nextSegmentId: 0, - allPendingTasks: 0, - pendingRootTasks: 0, - resources, - completedRootSegment: null, - abortableTasks: abortSet, - pingedTasks: pingedTasks, - clientRenderedBoundaries: [], - completedBoundaries: [], - partialBoundaries: [], - preamble: [], - postamble: [], - onError: onError === undefined ? defaultErrorHandler : onError, - onAllReady: onAllReady === undefined ? noop$2 : onAllReady, - onShellReady: onShellReady === undefined ? noop$2 : onShellReady, - onShellError: onShellError === undefined ? noop$2 : onShellError, - onFatalError: onFatalError === undefined ? noop$2 : onFatalError, - }; // This segment represents the root fallback. - - const rootSegment = createPendingSegment( - request, - 0, - null, - rootFormatContext, // Root segments are never embedded in Text on either edge - false, - false - ); // There is no parent so conceptually, we're unblocked to flush this segment. - - rootSegment.parentFlushed = true; - const rootTask = createTask( - request, - null, - children, - null, - rootSegment, - abortSet, - emptyContextObject, - rootContextSnapshot, - emptyTreeContext - ); - pingedTasks.push(rootTask); - return request; -} - -function pingTask(request, task) { - const pingedTasks = request.pingedTasks; - pingedTasks.push(task); - - if (pingedTasks.length === 1) { - scheduleWork(() => performWork(request)); - } -} - -function createSuspenseBoundary(request, fallbackAbortableTasks) { - return { - id: UNINITIALIZED_SUSPENSE_BOUNDARY_ID, - rootSegmentID: -1, - parentFlushed: false, - pendingTasks: 0, - forceClientRender: false, - completedSegments: [], - byteSize: 0, - fallbackAbortableTasks, - errorDigest: null, - resources: createBoundaryResources(), - }; -} - -function createTask( - request, - thenableState, - node, - blockedBoundary, - blockedSegment, - abortSet, - legacyContext, - context, - treeContext -) { - request.allPendingTasks++; - - if (blockedBoundary === null) { - request.pendingRootTasks++; - } else { - blockedBoundary.pendingTasks++; - } - - const task = { - node, - ping: () => pingTask(request, task), - blockedBoundary, - blockedSegment, - abortSet, - legacyContext, - context, - treeContext, - thenableState, - }; - - abortSet.add(task); - return task; -} - -function createPendingSegment( - request, - index, - boundary, - formatContext, - lastPushedText, - textEmbedded -) { - return { - status: PENDING, - id: -1, - // lazily assigned later - index, - parentFlushed: false, - chunks: [], - children: [], - formatContext, - boundary, - lastPushedText, - textEmbedded, - }; -} // DEV-only global reference to the currently executing task - -function pushFunctionComponentStackInDEV(task, type) {} - -function popComponentStackInDEV(task) {} // stash the component stack of an unwinding error until it is processed - -function logRecoverableError(request, error) { - // If this callback errors, we intentionally let that error bubble up to become a fatal error - // so that someone fixes the error reporting instead of hiding it. - const errorDigest = request.onError(error); - - if (errorDigest != null && typeof errorDigest !== 'string') { - // eslint-disable-next-line react-internal/prod-error-codes - throw new Error( - 'onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "' + - typeof errorDigest + - '" instead' - ); - } - - return errorDigest; -} - -function fatalError(request, error) { - // This is called outside error handling code such as if the root errors outside - // a suspense boundary or if the root suspense boundary's fallback errors. - // It's also called if React itself or its host configs errors. - const onShellError = request.onShellError; - onShellError(error); - const onFatalError = request.onFatalError; - onFatalError(error); - - if (request.destination !== null) { - request.status = CLOSED; - closeWithError(request.destination, error); - } else { - request.status = CLOSING; - request.fatalError = error; - } -} - -function renderSuspenseBoundary(request, task, props) { - const parentBoundary = task.blockedBoundary; - const parentSegment = task.blockedSegment; // Each time we enter a suspense boundary, we split out into a new segment for - // the fallback so that we can later replace that segment with the content. - // This also lets us split out the main content even if it doesn't suspend, - // in case it ends up generating a large subtree of content. - - const fallback = props.fallback; - const content = props.children; - const fallbackAbortSet = new Set(); - const newBoundary = createSuspenseBoundary(request, fallbackAbortSet); - const insertionIndex = parentSegment.chunks.length; // The children of the boundary segment is actually the fallback. - - const boundarySegment = createPendingSegment( - request, - insertionIndex, - newBoundary, - parentSegment.formatContext, // boundaries never require text embedding at their edges because comment nodes bound them - false, - false - ); - parentSegment.children.push(boundarySegment); // The parentSegment has a child Segment at this index so we reset the lastPushedText marker on the parent - - parentSegment.lastPushedText = false; // This segment is the actual child content. We can start rendering that immediately. - - const contentRootSegment = createPendingSegment( - request, - 0, - null, - parentSegment.formatContext, // boundaries never require text embedding at their edges because comment nodes bound them - false, - false - ); // We mark the root segment as having its parent flushed. It's not really flushed but there is - // no parent segment so there's nothing to wait on. - - contentRootSegment.parentFlushed = true; // Currently this is running synchronously. We could instead schedule this to pingedTasks. - // I suspect that there might be some efficiency benefits from not creating the suspended task - // and instead just using the stack if possible. - // TODO: Call this directly instead of messing with saving and restoring contexts. - // We can reuse the current context and task to render the content immediately without - // context switching. We just need to temporarily switch which boundary and which segment - // we're writing to. If something suspends, it'll spawn new suspended task with that context. - - task.blockedBoundary = newBoundary; - task.blockedSegment = contentRootSegment; - - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.resources, - newBoundary.resources - ); - } - - try { - // We use the safe form because we don't handle suspending here. Only error handling. - renderNode(request, task, content); - pushSegmentFinale( - contentRootSegment.chunks, - request.responseState, - contentRootSegment.lastPushedText, - contentRootSegment.textEmbedded - ); - contentRootSegment.status = COMPLETED; - - if (enableFloat) { - if (newBoundary.pendingTasks === 0) { - hoistCompletedBoundaryResources(request, newBoundary); - } - } - - queueCompletedSegment(newBoundary, contentRootSegment); - - if (newBoundary.pendingTasks === 0) { - // This must have been the last segment we were waiting on. This boundary is now complete. - // Therefore we won't need the fallback. We early return so that we don't have to create - // the fallback. - popComponentStackInDEV(task); - return; - } - } catch (error) { - contentRootSegment.status = ERRORED; - newBoundary.forceClientRender = true; - newBoundary.errorDigest = logRecoverableError(request, error); - // We don't need to schedule any task because we know the parent has written yet. - // We do need to fallthrough to create the fallback though. - } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.resources, - parentBoundary ? parentBoundary.resources : null - ); - } - - task.blockedBoundary = parentBoundary; - task.blockedSegment = parentSegment; - } // We create suspended task for the fallback because we don't want to actually work - // on it yet in case we finish the main content, so we queue for later. - - const suspendedFallbackTask = createTask( - request, - null, - fallback, - parentBoundary, - boundarySegment, - fallbackAbortSet, - task.legacyContext, - task.context, - task.treeContext - ); - // on preparing fallbacks if we don't have any more main content to task on. - - request.pingedTasks.push(suspendedFallbackTask); -} - -function hoistCompletedBoundaryResources(request, completedBoundary) { - if (request.completedRootSegment !== null || request.pendingRootTasks > 0) { - // The Shell has not flushed yet. we can hoist Resources for this boundary - // all the way to the Root. - hoistResourcesToRoot(request.resources, completedBoundary.resources); - } // We don't hoist if the root already flushed because late resources will be hoisted - // as boundaries flush -} - -function renderHostElement(request, task, type, props) { - const segment = task.blockedSegment; - const children = pushStartInstance( - segment.chunks, - request.preamble, - type, - props, - request.responseState, - segment.formatContext, - segment.lastPushedText - ); - segment.lastPushedText = false; - const prevContext = segment.formatContext; - segment.formatContext = getChildFormatContext(prevContext, type, props); // We use the non-destructive form because if something suspends, we still - // need to pop back up and finish this subtree of HTML. - - renderNode(request, task, children); // We expect that errors will fatal the whole task and that we don't need - // the correct context. Therefore this is not in a finally. - - segment.formatContext = prevContext; - pushEndInstance(segment.chunks, request.postamble, type); - segment.lastPushedText = false; -} - -function shouldConstruct(Component) { - return Component.prototype && Component.prototype.isReactComponent; -} - -function renderWithHooks( - request, - task, - prevThenableState, - Component, - props, - secondArg -) { - const componentIdentity = {}; - prepareToUseHooks(task, componentIdentity, prevThenableState); - const result = Component(props, secondArg); - return finishHooks(Component, props, result, secondArg); -} - -function finishClassComponent(request, task, instance, Component, props) { - const nextChildren = instance.render(); - - { - const childContextTypes = Component.childContextTypes; - - if (childContextTypes !== null && childContextTypes !== undefined) { - const previousContext = task.legacyContext; - const mergedContext = processChildContext( - instance, - Component, - previousContext, - childContextTypes - ); - task.legacyContext = mergedContext; - renderNodeDestructive(request, task, null, nextChildren); - task.legacyContext = previousContext; - return; - } - } - - renderNodeDestructive(request, task, null, nextChildren); -} - -function renderClassComponent(request, task, Component, props) { - const maskedContext = getMaskedContext(Component, task.legacyContext); - const instance = constructClassInstance(Component, props, maskedContext); - mountClassInstance(instance, Component, props, maskedContext); - finishClassComponent(request, task, instance, Component); -} -// components for some reason. - -function renderIndeterminateComponent( - request, - task, - prevThenableState, - Component, - props -) { - let legacyContext; - - { - legacyContext = getMaskedContext(Component, task.legacyContext); - } - - const value = renderWithHooks( - request, - task, - prevThenableState, - Component, - props, - legacyContext - ); - const hasId = checkDidRenderIdHook(); - - if ( - // Run these checks in production only if the flag is off. - // Eventually we'll delete this branch altogether. - typeof value === 'object' && - value !== null && - typeof value.render === 'function' && - value.$$typeof === undefined - ) { - mountClassInstance(value, Component, props, legacyContext); - finishClassComponent(request, task, value, Component); - } else { - // the previous task every again, so we can use the destructive recursive form. - - if (hasId) { - // This component materialized an id. We treat this as its own level, with - // a single "child" slot. - const prevTreeContext = task.treeContext; - const totalChildren = 1; - const index = 0; - task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index); - - try { - renderNodeDestructive(request, task, null, value); - } finally { - task.treeContext = prevTreeContext; - } - } else { - renderNodeDestructive(request, task, null, value); - } - } -} - -function resolveDefaultProps(Component, baseProps) { - if (Component && Component.defaultProps) { - // Resolve default props. Taken from ReactElement - const props = assign({}, baseProps); - const defaultProps = Component.defaultProps; - - for (const propName in defaultProps) { - if (props[propName] === undefined) { - props[propName] = defaultProps[propName]; - } - } - - return props; - } - - return baseProps; -} - -function renderForwardRef(request, task, prevThenableState, type, props, ref) { - pushFunctionComponentStackInDEV(task, type.render); - const children = renderWithHooks( - request, - task, - prevThenableState, - type.render, - props, - ref - ); - const hasId = checkDidRenderIdHook(); - - if (hasId) { - // This component materialized an id. We treat this as its own level, with - // a single "child" slot. - const prevTreeContext = task.treeContext; - const totalChildren = 1; - const index = 0; - task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index); - - try { - renderNodeDestructive(request, task, null, children); - } finally { - task.treeContext = prevTreeContext; - } - } else { - renderNodeDestructive(request, task, null, children); - } -} - -function renderMemo(request, task, prevThenableState, type, props, ref) { - const innerType = type.type; - const resolvedProps = resolveDefaultProps(innerType, props); - renderElement( - request, - task, - prevThenableState, - innerType, - resolvedProps, - ref - ); -} - -function renderContextConsumer(request, task, context, props) { - const render = props.children; - - const newValue = readContext(context); - const newChildren = render(newValue); - renderNodeDestructive(request, task, null, newChildren); -} - -function renderContextProvider(request, task, type, props) { - const context = type._context; - const value = props.value; - const children = props.children; - - task.context = pushProvider(context, value); - renderNodeDestructive(request, task, null, children); - task.context = popProvider(); -} - -function renderLazyComponent( - request, - task, - prevThenableState, - lazyComponent, - props, - ref -) { - const payload = lazyComponent._payload; - const init = lazyComponent._init; - const Component = init(payload); - const resolvedProps = resolveDefaultProps(Component, props); - renderElement( - request, - task, - prevThenableState, - Component, - resolvedProps, - ref - ); -} - -function renderOffscreen(request, task, props) { - const mode = props.mode; - - if (mode === 'hidden'); - else { - // A visible Offscreen boundary is treated exactly like a fragment: a - // pure indirection. - renderNodeDestructive(request, task, null, props.children); - } -} - -function renderElement(request, task, prevThenableState, type, props, ref) { - if (typeof type === 'function') { - if (shouldConstruct(type)) { - renderClassComponent(request, task, type, props); - return; - } else { - renderIndeterminateComponent( - request, - task, - prevThenableState, - type, - props - ); - return; - } - } - - if (typeof type === 'string') { - renderHostElement(request, task, type, props); - return; - } - - switch (type) { - // LegacyHidden acts the same as a fragment. This only works because we - // currently assume that every instance of LegacyHidden is accompanied by a - // host component wrapper. In the hidden mode, the host component is given a - // `hidden` attribute, which ensures that the initial HTML is not visible. - // To support the use of LegacyHidden as a true fragment, without an extra - // DOM node, we would have to hide the initial HTML in some other way. - // TODO: Delete in LegacyHidden. It's an unstable API only used in the - // www build. As a migration step, we could add a special prop to Offscreen - // that simulates the old behavior (no hiding, no change to effects). - case REACT_LEGACY_HIDDEN_TYPE: - case REACT_DEBUG_TRACING_MODE_TYPE: - case REACT_STRICT_MODE_TYPE: - case REACT_PROFILER_TYPE: - case REACT_FRAGMENT_TYPE: { - renderNodeDestructive(request, task, null, props.children); - return; - } - - case REACT_OFFSCREEN_TYPE: { - renderOffscreen(request, task, props); - return; - } - - case REACT_SUSPENSE_LIST_TYPE: { - renderNodeDestructive(request, task, null, props.children); - return; - } - - case REACT_SCOPE_TYPE: { - throw new Error('ReactDOMServer does not yet support scope components.'); - } - // eslint-disable-next-line-no-fallthrough - - case REACT_SUSPENSE_TYPE: { - { - renderSuspenseBoundary(request, task, props); - } - - return; - } - } - - if (typeof type === 'object' && type !== null) { - switch (type.$$typeof) { - case REACT_FORWARD_REF_TYPE: { - renderForwardRef(request, task, prevThenableState, type, props, ref); - return; - } - - case REACT_MEMO_TYPE: { - renderMemo(request, task, prevThenableState, type, props, ref); - return; - } - - case REACT_PROVIDER_TYPE: { - renderContextProvider(request, task, type, props); - return; - } - - case REACT_CONTEXT_TYPE: { - renderContextConsumer(request, task, type, props); - return; - } - - case REACT_LAZY_TYPE: { - renderLazyComponent(request, task, prevThenableState, type, props); - return; - } - } - } - - let info = ''; - - throw new Error( - 'Element type is invalid: expected a string (for built-in ' + - 'components) or a class/function (for composite components) ' + - ('but got: ' + (type == null ? type : typeof type) + '.' + info) - ); -} - -function renderNodeDestructive( - request, - task, // The thenable state reused from the previous attempt, if any. This is almost - // always null, except when called by retryTask. - prevThenableState, - node -) { - { - return renderNodeDestructiveImpl(request, task, prevThenableState, node); - } -} // This function by it self renders a node and consumes the task by mutating it -// to update the current execution state. - -function renderNodeDestructiveImpl(request, task, prevThenableState, node) { - // Stash the node we're working on. We'll pick up from this task in case - // something suspends. - task.node = node; // Handle object types - - if (typeof node === 'object' && node !== null) { - switch (node.$$typeof) { - case REACT_ELEMENT_TYPE: { - const element = node; - const type = element.type; - const props = element.props; - const ref = element.ref; - renderElement(request, task, prevThenableState, type, props, ref); - return; - } - - case REACT_PORTAL_TYPE: - throw new Error( - 'Portals are not currently supported by the server renderer. ' + - 'Render them conditionally so that they only appear on the client render.' - ); - // eslint-disable-next-line-no-fallthrough - - case REACT_LAZY_TYPE: { - const lazyNode = node; - const payload = lazyNode._payload; - const init = lazyNode._init; - let resolvedNode; - - { - resolvedNode = init(payload); - } - - renderNodeDestructive(request, task, null, resolvedNode); - return; - } - } - - if (isArray(node)) { - renderChildrenArray(request, task, node); - return; - } - - const iteratorFn = getIteratorFn(node); - - if (iteratorFn) { - const iterator = iteratorFn.call(node); - - if (iterator) { - // We need to know how many total children are in this set, so that we - // can allocate enough id slots to acommodate them. So we must exhaust - // the iterator before we start recursively rendering the children. - // TODO: This is not great but I think it's inherent to the id - // generation algorithm. - let step = iterator.next(); // If there are not entries, we need to push an empty so we start by checking that. - - if (!step.done) { - const children = []; - - do { - children.push(step.value); - step = iterator.next(); - } while (!step.done); - - renderChildrenArray(request, task, children); - return; - } - - return; - } - } // $FlowFixMe[method-unbinding] - - const childString = Object.prototype.toString.call(node); - throw new Error( - 'Objects are not valid as a React child (found: ' + - (childString === '[object Object]' - ? 'object with keys {' + Object.keys(node).join(', ') + '}' - : childString) + - '). ' + - 'If you meant to render a collection of children, use an array ' + - 'instead.' - ); - } - - if (typeof node === 'string') { - const segment = task.blockedSegment; - segment.lastPushedText = pushTextInstance( - task.blockedSegment.chunks, - node, - request.responseState, - segment.lastPushedText - ); - return; - } - - if (typeof node === 'number') { - const segment = task.blockedSegment; - segment.lastPushedText = pushTextInstance( - task.blockedSegment.chunks, - '' + node, - request.responseState, - segment.lastPushedText - ); - return; - } -} - -function renderChildrenArray(request, task, children) { - const totalChildren = children.length; - - for (let i = 0; i < totalChildren; i++) { - const prevTreeContext = task.treeContext; - task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i); - - try { - // We need to use the non-destructive form so that we can safely pop back - // up and render the sibling if something suspends. - renderNode(request, task, children[i]); - } finally { - task.treeContext = prevTreeContext; - } - } -} - -function spawnNewSuspendedTask(request, task, thenableState, x) { - // Something suspended, we'll need to create a new segment and resolve it later. - const segment = task.blockedSegment; - const insertionIndex = segment.chunks.length; - const newSegment = createPendingSegment( - request, - insertionIndex, - null, - segment.formatContext, // Adopt the parent segment's leading text embed - segment.lastPushedText, // Assume we are text embedded at the trailing edge - true - ); - segment.children.push(newSegment); // Reset lastPushedText for current Segment since the new Segment "consumed" it - - segment.lastPushedText = false; - const newTask = createTask( - request, - thenableState, - task.node, - task.blockedBoundary, - newSegment, - task.abortSet, - task.legacyContext, - task.context, - task.treeContext - ); - - const ping = newTask.ping; - x.then(ping, ping); -} // This is a non-destructive form of rendering a node. If it suspends it spawns -// a new task and restores the context of this task to what it was before. - -function renderNode(request, task, node) { - // TODO: Store segment.children.length here and reset it in case something - // suspended partially through writing something. - // Snapshot the current context in case something throws to interrupt the - // process. - const previousFormatContext = task.blockedSegment.formatContext; - const previousLegacyContext = task.legacyContext; - const previousContext = task.context; - - try { - return renderNodeDestructive(request, task, null, node); - } catch (x) { - resetHooksState(); - - if (typeof x === 'object' && x !== null && typeof x.then === 'function') { - const thenableState = getThenableStateAfterSuspending(); - spawnNewSuspendedTask(request, task, thenableState, x); // Restore the context. We assume that this will be restored by the inner - // functions in case nothing throws so we don't use "finally" here. - - task.blockedSegment.formatContext = previousFormatContext; - task.legacyContext = previousLegacyContext; - task.context = previousContext; // Restore all active ReactContexts to what they were before. - - switchContext(previousContext); - - return; - } else { - // Restore the context. We assume that this will be restored by the inner - // functions in case nothing throws so we don't use "finally" here. - task.blockedSegment.formatContext = previousFormatContext; - task.legacyContext = previousLegacyContext; - task.context = previousContext; // Restore all active ReactContexts to what they were before. - - switchContext(previousContext); - // Let's terminate the rest of the tree and don't render any siblings. - - throw x; - } - } -} - -function erroredTask(request, boundary, segment, error) { - // Report the error to a global handler. - const errorDigest = logRecoverableError(request, error); - - if (boundary === null) { - fatalError(request, error); - } else { - boundary.pendingTasks--; - - if (!boundary.forceClientRender) { - boundary.forceClientRender = true; - boundary.errorDigest = errorDigest; - // so we can flush it, if the parent already flushed. - - if (boundary.parentFlushed) { - // We don't have a preference where in the queue this goes since it's likely - // to error on the client anyway. However, intentionally client-rendered - // boundaries should be flushed earlier so that they can start on the client. - // We reuse the same queue for errors. - request.clientRenderedBoundaries.push(boundary); - } - } - } - - request.allPendingTasks--; - - if (request.allPendingTasks === 0) { - const onAllReady = request.onAllReady; - onAllReady(); - } -} - -function abortTaskSoft(task) { - // This aborts task without aborting the parent boundary that it blocks. - // It's used for when we didn't need this task to complete the tree. - // If task was needed, then it should use abortTask instead. - const request = this; - const boundary = task.blockedBoundary; - const segment = task.blockedSegment; - segment.status = ABORTED; - finishedTask(request, boundary, segment); -} - -function abortTask(task, request, error) { - // This aborts the task and aborts the parent that it blocks, putting it into - // client rendered mode. - const boundary = task.blockedBoundary; - const segment = task.blockedSegment; - segment.status = ABORTED; - - if (boundary === null) { - request.allPendingTasks--; // We didn't complete the root so we have nothing to show. We can close - // the request; - - if (request.status !== CLOSING && request.status !== CLOSED) { - logRecoverableError(request, error); - fatalError(request, error); - } - } else { - boundary.pendingTasks--; - - if (!boundary.forceClientRender) { - boundary.forceClientRender = true; - boundary.errorDigest = request.onError(error); - - if (boundary.parentFlushed) { - request.clientRenderedBoundaries.push(boundary); - } - } // If this boundary was still pending then we haven't already cancelled its fallbacks. - // We'll need to abort the fallbacks, which will also error that parent boundary. - - boundary.fallbackAbortableTasks.forEach((fallbackTask) => - abortTask(fallbackTask, request, error) - ); - boundary.fallbackAbortableTasks.clear(); - request.allPendingTasks--; - - if (request.allPendingTasks === 0) { - const onAllReady = request.onAllReady; - onAllReady(); - } - } -} - -function queueCompletedSegment(boundary, segment) { - if ( - segment.chunks.length === 0 && - segment.children.length === 1 && - segment.children[0].boundary === null - ) { - // This is an empty segment. There's nothing to write, so we can instead transfer the ID - // to the child. That way any existing references point to the child. - const childSegment = segment.children[0]; - childSegment.id = segment.id; - childSegment.parentFlushed = true; - - if (childSegment.status === COMPLETED) { - queueCompletedSegment(boundary, childSegment); - } - } else { - const completedSegments = boundary.completedSegments; - completedSegments.push(segment); - } -} - -function finishedTask(request, boundary, segment) { - if (boundary === null) { - if (segment.parentFlushed) { - if (request.completedRootSegment !== null) { - throw new Error( - 'There can only be one root segment. This is a bug in React.' - ); - } - - request.completedRootSegment = segment; - } - - request.pendingRootTasks--; - - if (request.pendingRootTasks === 0) { - // We have completed the shell so the shell can't error anymore. - request.onShellError = noop$2; - const onShellReady = request.onShellReady; - onShellReady(); - } - } else { - boundary.pendingTasks--; - - if (boundary.forceClientRender); - else if (boundary.pendingTasks === 0) { - // This must have been the last segment we were waiting on. This boundary is now complete. - if (segment.parentFlushed) { - // Our parent segment already flushed, so we need to schedule this segment to be emitted. - // If it is a segment that was aborted, we'll write other content instead so we don't need - // to emit it. - if (segment.status === COMPLETED) { - queueCompletedSegment(boundary, segment); - } - } - - { - hoistCompletedBoundaryResources(request, boundary); - } - - if (boundary.parentFlushed) { - // The segment might be part of a segment that didn't flush yet, but if the boundary's - // parent flushed, we need to schedule the boundary to be emitted. - request.completedBoundaries.push(boundary); - } // We can now cancel any pending task on the fallback since we won't need to show it anymore. - // This needs to happen after we read the parentFlushed flags because aborting can finish - // work which can trigger user code, which can start flushing, which can change those flags. - - boundary.fallbackAbortableTasks.forEach(abortTaskSoft, request); - boundary.fallbackAbortableTasks.clear(); - } else { - if (segment.parentFlushed) { - // Our parent already flushed, so we need to schedule this segment to be emitted. - // If it is a segment that was aborted, we'll write other content instead so we don't need - // to emit it. - if (segment.status === COMPLETED) { - queueCompletedSegment(boundary, segment); - const completedSegments = boundary.completedSegments; - - if (completedSegments.length === 1) { - // This is the first time since we last flushed that we completed anything. - // We can schedule this boundary to emit its partially completed segments early - // in case the parent has already been flushed. - if (boundary.parentFlushed) { - request.partialBoundaries.push(boundary); - } - } - } - } - } - } - - request.allPendingTasks--; - - if (request.allPendingTasks === 0) { - // This needs to be called at the very end so that we can synchronously write the result - // in the callback if needed. - const onAllReady = request.onAllReady; - onAllReady(); - } -} - -function retryTask(request, task) { - { - const blockedBoundary = task.blockedBoundary; - setCurrentlyRenderingBoundaryResourcesTarget( - request.resources, - blockedBoundary ? blockedBoundary.resources : null - ); - } - - const segment = task.blockedSegment; - - if (segment.status !== PENDING) { - // We completed this by other means before we had a chance to retry it. - return; - } // We restore the context to what it was when we suspended. - // We don't restore it after we leave because it's likely that we'll end up - // needing a very similar context soon again. - - switchContext(task.context); - - try { - // We call the destructive form that mutates this task. That way if something - // suspends again, we can reuse the same task instead of spawning a new one. - // Reset the task's thenable state before continuing, so that if a later - // component suspends we can reuse the same task object. If the same - // component suspends again, the thenable state will be restored. - const prevThenableState = task.thenableState; - task.thenableState = null; - renderNodeDestructive(request, task, prevThenableState, task.node); - pushSegmentFinale( - segment.chunks, - request.responseState, - segment.lastPushedText, - segment.textEmbedded - ); - task.abortSet.delete(task); - segment.status = COMPLETED; - finishedTask(request, task.blockedBoundary, segment); - } catch (x) { - resetHooksState(); - - if (typeof x === 'object' && x !== null && typeof x.then === 'function') { - // Something suspended again, let's pick it back up later. - const ping = task.ping; - x.then(ping, ping); - task.thenableState = getThenableStateAfterSuspending(); - } else { - task.abortSet.delete(task); - segment.status = ERRORED; - erroredTask(request, task.blockedBoundary, segment, x); - } - } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget(request.resources, null); - } - } -} - -function performWork(request) { - if (request.status === CLOSED) { - return; - } - - const prevContext = getActiveContext(); - const prevDispatcher = ReactCurrentDispatcher$1.current; - ReactCurrentDispatcher$1.current = HooksDispatcher; - let prevCacheDispatcher; - - { - prevCacheDispatcher = ReactCurrentCache.current; - ReactCurrentCache.current = DefaultCacheDispatcher; - } - - const previousHostDispatcher = prepareToRender(request.resources); - - const prevResponseState = currentResponseState; - setCurrentResponseState(request.responseState); - - try { - const pingedTasks = request.pingedTasks; - let i; - - for (i = 0; i < pingedTasks.length; i++) { - const task = pingedTasks[i]; - retryTask(request, task); - } - - pingedTasks.splice(0, i); - - if (request.destination !== null) { - flushCompletedQueues(request, request.destination); - } - } catch (error) { - logRecoverableError(request, error); - fatalError(request, error); - } finally { - setCurrentResponseState(prevResponseState); - ReactCurrentDispatcher$1.current = prevDispatcher; - - { - ReactCurrentCache.current = prevCacheDispatcher; - } - - cleanupAfterRender(previousHostDispatcher); - - if (prevDispatcher === HooksDispatcher) { - // This means that we were in a reentrant work loop. This could happen - // in a renderer that supports synchronous work like renderToString, - // when it's called from within another renderer. - // Normally we don't bother switching the contexts to their root/default - // values when leaving because we'll likely need the same or similar - // context again. However, when we're inside a synchronous loop like this - // we'll to restore the context to what it was before returning. - switchContext(prevContext); - } - } -} - -function flushSubtree(request, destination, segment) { - segment.parentFlushed = true; - - switch (segment.status) { - case PENDING: { - // We're emitting a placeholder for this segment to be filled in later. - // Therefore we'll need to assign it an ID - to refer to it by. - const segmentID = (segment.id = request.nextSegmentId++); // When this segment finally completes it won't be embedded in text since it will flush separately - - segment.lastPushedText = false; - segment.textEmbedded = false; - return writePlaceholder(destination, request.responseState, segmentID); - } - - case COMPLETED: { - segment.status = FLUSHED; - let r = true; - const chunks = segment.chunks; - let chunkIdx = 0; - const children = segment.children; - - for (let childIdx = 0; childIdx < children.length; childIdx++) { - const nextChild = children[childIdx]; // Write all the chunks up until the next child. - - for (; chunkIdx < nextChild.index; chunkIdx++) { - writeChunk(destination, chunks[chunkIdx]); - } - - r = flushSegment(request, destination, nextChild); - } // Finally just write all the remaining chunks - - for (; chunkIdx < chunks.length - 1; chunkIdx++) { - writeChunk(destination, chunks[chunkIdx]); - } - - if (chunkIdx < chunks.length) { - r = writeChunkAndReturn(destination, chunks[chunkIdx]); - } - - return r; - } - - default: { - throw new Error( - 'Aborted, errored or already flushed boundaries should not be flushed again. This is a bug in React.' - ); - } - } -} - -function flushSegment(request, destination, segment) { - const boundary = segment.boundary; - - if (boundary === null) { - // Not a suspense boundary. - return flushSubtree(request, destination, segment); - } - - boundary.parentFlushed = true; // This segment is a Suspense boundary. We need to decide whether to - // emit the content or the fallback now. - - if (boundary.forceClientRender) { - // Emit a client rendered suspense boundary wrapper. - // We never queue the inner boundary so we'll never emit its content or partial segments. - writeStartClientRenderedSuspenseBoundary( - destination, - request.responseState, - boundary.errorDigest, - boundary.errorMessage, - boundary.errorComponentStack - ); // Flush the fallback. - - flushSubtree(request, destination, segment); - return writeEndClientRenderedSuspenseBoundary( - destination, - request.responseState - ); - } else if (boundary.pendingTasks > 0) { - // This boundary is still loading. Emit a pending suspense boundary wrapper. - // Assign an ID to refer to the future content by. - boundary.rootSegmentID = request.nextSegmentId++; - - if (boundary.completedSegments.length > 0) { - // If this is at least partially complete, we can queue it to be partially emitted early. - request.partialBoundaries.push(boundary); - } /// This is the first time we should have referenced this ID. - - const id = (boundary.id = assignSuspenseBoundaryID(request.responseState)); - writeStartPendingSuspenseBoundary(destination, request.responseState, id); // Flush the fallback. - - flushSubtree(request, destination, segment); - return writeEndPendingSuspenseBoundary(destination, request.responseState); - } else if (boundary.byteSize > request.progressiveChunkSize) { - // This boundary is large and will be emitted separately so that we can progressively show - // other content. We add it to the queue during the flush because we have to ensure that - // the parent flushes first so that there's something to inject it into. - // We also have to make sure that it's emitted into the queue in a deterministic slot. - // I.e. we can't insert it here when it completes. - // Assign an ID to refer to the future content by. - boundary.rootSegmentID = request.nextSegmentId++; - request.completedBoundaries.push(boundary); // Emit a pending rendered suspense boundary wrapper. - - writeStartPendingSuspenseBoundary( - destination, - request.responseState, - boundary.id - ); // Flush the fallback. - - flushSubtree(request, destination, segment); - return writeEndPendingSuspenseBoundary(destination, request.responseState); - } else { - { - hoistResources(request.resources, boundary.resources); - } // We can inline this boundary's content as a complete boundary. - - writeStartCompletedSuspenseBoundary(destination, request.responseState); - const completedSegments = boundary.completedSegments; - - if (completedSegments.length !== 1) { - throw new Error( - 'A previously unvisited boundary must have exactly one root segment. This is a bug in React.' - ); - } - - const contentSegment = completedSegments[0]; - flushSegment(request, destination, contentSegment); - return writeEndCompletedSuspenseBoundary( - destination, - request.responseState - ); - } -} - -function flushInitialResources(destination, resources, responseState) { - writeInitialResources(destination, resources, responseState); -} - -function flushImmediateResources(destination, request) { - writeImmediateResources( - destination, - request.resources, - request.responseState - ); -} - -function flushClientRenderedBoundary(request, destination, boundary) { - return writeClientRenderBoundaryInstruction( - destination, - request.responseState, - boundary.id, - boundary.errorDigest, - boundary.errorMessage, - boundary.errorComponentStack - ); -} - -function flushSegmentContainer(request, destination, segment) { - writeStartSegment( - destination, - request.responseState, - segment.formatContext, - segment.id - ); - flushSegment(request, destination, segment); - return writeEndSegment(destination, segment.formatContext); -} - -function flushCompletedBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.resources, - boundary.resources - ); - } - - const completedSegments = boundary.completedSegments; - let i = 0; - - for (; i < completedSegments.length; i++) { - const segment = completedSegments[i]; - flushPartiallyCompletedSegment(request, destination, boundary, segment); - } - - completedSegments.length = 0; - return writeCompletedBoundaryInstruction( - destination, - request.responseState, - boundary.id, - boundary.rootSegmentID, - boundary.resources - ); -} - -function flushPartialBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.resources, - boundary.resources - ); - } - - const completedSegments = boundary.completedSegments; - let i = 0; - - for (; i < completedSegments.length; i++) { - const segment = completedSegments[i]; - - if ( - !flushPartiallyCompletedSegment(request, destination, boundary, segment) - ) { - i++; - completedSegments.splice(0, i); // Only write as much as the buffer wants. Something higher priority - // might want to write later. - - return false; - } - } - - completedSegments.splice(0, i); - return true; -} - -function flushPartiallyCompletedSegment( - request, - destination, - boundary, - segment -) { - if (segment.status === FLUSHED) { - // We've already flushed this inline. - return true; - } - - const segmentID = segment.id; - - if (segmentID === -1) { - // This segment wasn't previously referred to. This happens at the root of - // a boundary. We make kind of a leap here and assume this is the root. - const rootSegmentID = (segment.id = boundary.rootSegmentID); - - if (rootSegmentID === -1) { - throw new Error( - 'A root segment ID must have been assigned by now. This is a bug in React.' - ); - } - - return flushSegmentContainer(request, destination, segment); - } else { - flushSegmentContainer(request, destination, segment); - return writeCompletedSegmentInstruction( - destination, - request.responseState, - segmentID - ); - } -} - -function flushCompletedQueues(request, destination) { - try { - // The structure of this is to go through each queue one by one and write - // until the sink tells us to stop. When we should stop, we still finish writing - // that item fully and then yield. At that point we remove the already completed - // items up until the point we completed them. - let i; - const completedRootSegment = request.completedRootSegment; - - if (completedRootSegment !== null) { - if (request.pendingRootTasks === 0) { - if (enableFloat) { - const preamble = request.preamble; - - for (i = 0; i < preamble.length; i++) { - // we expect the preamble to be tiny and will ignore backpressure - writeChunk(destination, preamble[i]); - } - - flushInitialResources( - destination, - request.resources, - request.responseState - ); - } - - flushSegment(request, destination, completedRootSegment); - request.completedRootSegment = null; - writeCompletedRoot(destination, request.responseState); - } else { - // We haven't flushed the root yet so we don't need to check any other branches further down - return; - } - } else if (enableFloat) { - flushImmediateResources(destination, request); - } // We emit client rendering instructions for already emitted boundaries first. - // This is so that we can signal to the client to start client rendering them as - // soon as possible. - - const clientRenderedBoundaries = request.clientRenderedBoundaries; - - for (i = 0; i < clientRenderedBoundaries.length; i++) { - const boundary = clientRenderedBoundaries[i]; - - if (!flushClientRenderedBoundary(request, destination, boundary)) { - request.destination = null; - i++; - clientRenderedBoundaries.splice(0, i); - return; - } - } - - clientRenderedBoundaries.splice(0, i); // Next we emit any complete boundaries. It's better to favor boundaries - // that are completely done since we can actually show them, than it is to emit - // any individual segments from a partially complete boundary. - - const completedBoundaries = request.completedBoundaries; - - for (i = 0; i < completedBoundaries.length; i++) { - const boundary = completedBoundaries[i]; - - if (!flushCompletedBoundary(request, destination, boundary)) { - request.destination = null; - i++; - completedBoundaries.splice(0, i); - return; - } - } - - completedBoundaries.splice(0, i); // Allow anything written so far to flush to the underlying sink before - // we continue with lower priorities. - - completeWriting(destination); - beginWriting(destination); // TODO: Here we'll emit data used by hydration. - // Next we emit any segments of any boundaries that are partially complete - // but not deeply complete. - - const partialBoundaries = request.partialBoundaries; - - for (i = 0; i < partialBoundaries.length; i++) { - const boundary = partialBoundaries[i]; - - if (!flushPartialBoundary(request, destination, boundary)) { - request.destination = null; - i++; - partialBoundaries.splice(0, i); - return; - } - } - - partialBoundaries.splice(0, i); // Next we check the completed boundaries again. This may have had - // boundaries added to it in case they were too larged to be inlined. - // New ones might be added in this loop. - - const largeBoundaries = request.completedBoundaries; - - for (i = 0; i < largeBoundaries.length; i++) { - const boundary = largeBoundaries[i]; - - if (!flushCompletedBoundary(request, destination, boundary)) { - request.destination = null; - i++; - largeBoundaries.splice(0, i); - return; - } - } - - largeBoundaries.splice(0, i); - } finally { - if ( - request.allPendingTasks === 0 && - request.pingedTasks.length === 0 && - request.clientRenderedBoundaries.length === 0 && - request.completedBoundaries.length === 0 // We don't need to check any partially completed segments because - // either they have pending task or they're complete. - ) { - { - const postamble = request.postamble; - - for (let i = 0; i < postamble.length; i++) { - writeChunk(destination, postamble[i]); - } - } - - close(destination); - } - } -} - -function startWork(request) { - scheduleWork(() => performWork(request)); -} -function startFlowing(request, destination) { - if (request.status === CLOSING) { - request.status = CLOSED; - closeWithError(destination, request.fatalError); - return; - } - - if (request.status === CLOSED) { - return; - } - - if (request.destination !== null) { - // We're already flowing. - return; - } - - request.destination = destination; - - try { - flushCompletedQueues(request, destination); - } catch (error) { - logRecoverableError(request, error); - fatalError(request, error); - } -} // This is called to early terminate a request. It puts all pending boundaries in client rendered state. - -function abort(request, reason) { - try { - const abortableTasks = request.abortableTasks; - - if (abortableTasks.size > 0) { - const error = - reason === undefined - ? new Error('The render was aborted by the server without a reason.') - : reason; - abortableTasks.forEach((task) => abortTask(task, request, error)); - abortableTasks.clear(); - } - - if (request.destination !== null) { - flushCompletedQueues(request, request.destination); - } - } catch (error) { - logRecoverableError(request, error); - fatalError(request, error); - } -} - -function renderToReadableStream(children, options) { - return new Promise((resolve, reject) => { - let onFatalError; - let onAllReady; - const allReady = new Promise((res, rej) => { - onAllReady = res; - onFatalError = rej; - }); - - function onShellReady() { - const stream = new ReadableStream( - { - type: 'direct', - pull: (controller) => { - // $FlowIgnore - startFlowing(request, controller); - }, - cancel: (reason) => { - abort(request); - }, - }, // $FlowFixMe size() methods are not allowed on byte streams. - { - highWaterMark: 2048, - } - ); // TODO: Move to sub-classing ReadableStream. - - stream.allReady = allReady; - resolve(stream); - } - - function onShellError(error) { - // If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`. - // However, `allReady` will be rejected by `onFatalError` as well. - // So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`. - allReady.catch(() => {}); - reject(error); - } - - const request = createRequest( - children, - createResponseState( - options ? options.identifierPrefix : undefined, - options ? options.nonce : undefined, - options ? options.bootstrapScriptContent : undefined, - options ? options.bootstrapScripts : undefined, - options ? options.bootstrapModules : undefined, - options ? options.unstable_externalRuntimeSrc : undefined - ), - createRootFormatContext(options ? options.namespaceURI : undefined), - options ? options.progressiveChunkSize : undefined, - options ? options.onError : undefined, - onAllReady, - onShellReady, - onShellError, - onFatalError - ); - - if (options && options.signal) { - const signal = options.signal; - - if (signal.aborted) { - abort(request, signal.reason); - } else { - const listener = () => { - abort(request, signal.reason); - signal.removeEventListener('abort', listener); - }; - - signal.addEventListener('abort', listener); - } - } - - startWork(request); - }); -} - -export {renderToReadableStream, ReactVersion as version}; |