From d8555ff650108577502c1c611b9aa8c371ebf38b Mon Sep 17 00:00:00 2001 From: gnoff Date: Mon, 6 Mar 2023 23:05:43 +0000 Subject: [PATCH] [Float][Fizz][Fiber] - Do not hoist elements with `itemProp` & hydrate more tolerantly in hoist contexts (#26256) ## Do not hoist elements with `itemProp` In HTML `itemprop` signifies a property of an `itemscope` with respect to the Microdata spec (https://html.spec.whatwg.org/multipage/microdata.html#microdata) additionally `itemprop` is valid on any tag and can even make some tags that are otherwise invalid in the `` valid there (`` for instance). Originally I tried an approach where if you rendered something otherwise hoistable inside an `itemscope` it would not hoist if it had an `itemprop`. This meant that some components with `itemprop` could hoist (if they were not scoped, which is generally invalid microdata implementation). However the problem is things that do hoist, hoist into the head and body and these tags can have an `itemscope`. This creates a ton of ambiguity when trying to hydrate in these hoist scopes because we can't know for certain whether a DOM node we find there was hoisted or not even if it has an `itemprop` attribute. There are other scenarios too that have abiguous semantics like rendering a hoistable with `itemProp` outside of ``. Is it fair to embed that hoistable inside that itemScope even though it was defined outside? To simplify the situation and disambiguate I dropped the `itemscope` portion from the implementation and now any host component that could normally be hoisted will not hoist if it has an `itemProp` prop. In addition to the changes made for `itemProp` this PR also modifies part of the hydration implementation to be more tolerant of tags injected by 3rd parties. This was opportunistically done when we needed to have context information like `inItemScope` but with the most recent implementation that has been removed. I have however left the hydration changes in place as it is a goal to make React handle hydrating the entire Document even when we cannot control whether 3rd parties are going to inject tags that React will not render but are also not hoistables ------- ##### Original Description when we considered tracking itemScope >One recent decision was to make elements using the `itemProp` prop not hoistable if they were inside and itemScope. This better fits with Microdata spec which allows for meta tags and other tag types usually reserved for the `` to be used in the `` when using itemScope. > >To implement this a number of small changes were necessary > >1. HostContext in prod needed to expand beyond just tracking the element namespace for new element creation. It now tracks whether we are in an itemScope. To keep this efficient it is modeled as a bitmask. >2. To disambiguate what is and is not a potential instance in the DOM for hoistables the hydration algo was updated to skip past non-matching instances while attempting to claim the instance rather than ahead of time (getNextHydratable). >3. React will not consider an itemScope on ``, ``, or `` as a valid scope for the hoisting opt-out. This is important as an invariant so we can make assumptions about certain tags in these scopes. This should not be a functional breaking change because if any of these tags have an `itemScope` then it can just be moved into the first node inside the `` > >Since we were already updating the logic for hydration to better support `itemScope` opt-out I also changed the hydration behavior for suspected 3rd party nodes in `` and ``. Now if you are hydrating in either of those contexts hydration will skip past any non-matching nodes until it finds a match. This allows 3rd party scripts and extensions to inject nodes in either context that React does not expect and still avoid a hydration mismatch. > >This new algorithm isn't perfect and it is possible for a mismatch to occur. The most glaring case may be if a 3rd party script prepends a `
` into `` and you render a `
` in `` in your app. there is nothing to signal to React that this div was 3rd party so it will claim is as the hydrated instance and hydration will almost certainly fail immediately afterwards. > >The expectation is that this is rare and that if falling back to client rendering is transparent to the user then there is not problem here. We will continue to evaluate this and may change the hydration matching algorithm further to match user and developer expectations DiffTrain build for [8a9f82ed58c2fa76583a041fa34ad80f5f94a3d1](https://github.com/facebook/react/commit/8a9f82ed58c2fa76583a041fa34ad80f5f94a3d1) --- compiled/facebook-www/REVISION | 2 +- compiled/facebook-www/React-dev.modern.js | 2 +- compiled/facebook-www/ReactART-dev.classic.js | 2 +- compiled/facebook-www/ReactART-prod.modern.js | 4 +- compiled/facebook-www/ReactDOM-dev.classic.js | 1158 ++++++++++------ compiled/facebook-www/ReactDOM-dev.modern.js | 1170 ++++++++++------ .../facebook-www/ReactDOM-prod.classic.js | 1051 +++++++------- compiled/facebook-www/ReactDOM-prod.modern.js | 1009 ++++++++------ .../ReactDOM-profiling.classic.js | 1095 ++++++++------- .../facebook-www/ReactDOM-profiling.modern.js | 1055 +++++++------- .../ReactDOMServer-dev.classic.js | 17 +- .../facebook-www/ReactDOMServer-dev.modern.js | 17 +- .../ReactDOMServer-prod.classic.js | 13 +- .../ReactDOMServer-prod.modern.js | 13 +- .../ReactDOMServerStreaming-dev.modern.js | 15 +- .../ReactDOMServerStreaming-prod.modern.js | 11 +- .../ReactDOMTesting-dev.classic.js | 1220 ++++++++++------- .../ReactDOMTesting-dev.modern.js | 1154 ++++++++++------ .../ReactDOMTesting-prod.classic.js | 901 ++++++------ .../ReactDOMTesting-prod.modern.js | 981 +++++++------ .../ReactTestRenderer-dev.modern.js | 2 +- compiled/facebook-www/WARNINGS | 1 + 22 files changed, 6344 insertions(+), 4549 deletions(-) diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index 1d1968d832476..fd52e8177fc55 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -3cad3a54eda7b2d1c670c2d414f33d78a4c3f6af +8a9f82ed58c2fa76583a041fa34ad80f5f94a3d1 diff --git a/compiled/facebook-www/React-dev.modern.js b/compiled/facebook-www/React-dev.modern.js index e493094f51b12..dccf54efd8a4c 100644 --- a/compiled/facebook-www/React-dev.modern.js +++ b/compiled/facebook-www/React-dev.modern.js @@ -27,7 +27,7 @@ if ( } "use strict"; -var ReactVersion = "18.3.0-www-modern-2b948334"; +var ReactVersion = "18.3.0-www-modern-49cbcd5e"; // ATTENTION // When adding new symbols to this file, diff --git a/compiled/facebook-www/ReactART-dev.classic.js b/compiled/facebook-www/ReactART-dev.classic.js index f25d86446a3a1..5034b942fc239 100644 --- a/compiled/facebook-www/ReactART-dev.classic.js +++ b/compiled/facebook-www/ReactART-dev.classic.js @@ -69,7 +69,7 @@ function _assertThisInitialized(self) { return self; } -var ReactVersion = "18.3.0-www-classic-41d9d61e"; +var ReactVersion = "18.3.0-www-classic-62fa5a08"; var LegacyRoot = 0; var ConcurrentRoot = 1; diff --git a/compiled/facebook-www/ReactART-prod.modern.js b/compiled/facebook-www/ReactART-prod.modern.js index 0e1485c074544..808e566a39cf7 100644 --- a/compiled/facebook-www/ReactART-prod.modern.js +++ b/compiled/facebook-www/ReactART-prod.modern.js @@ -9448,7 +9448,7 @@ var slice = Array.prototype.slice, return null; }, bundleType: 0, - version: "18.3.0-www-modern-2190fcfa", + version: "18.3.0-www-modern-20b8c9c5", rendererPackageName: "react-art" }; var internals$jscomp$inline_1280 = { @@ -9479,7 +9479,7 @@ var internals$jscomp$inline_1280 = { scheduleRoot: null, setRefreshHandler: null, getCurrentFiber: null, - reconcilerVersion: "18.3.0-www-modern-2190fcfa" + reconcilerVersion: "18.3.0-www-modern-20b8c9c5" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { var hook$jscomp$inline_1281 = __REACT_DEVTOOLS_GLOBAL_HOOK__; diff --git a/compiled/facebook-www/ReactDOM-dev.classic.js b/compiled/facebook-www/ReactDOM-dev.classic.js index fcd001041171d..1b3492abaca23 100644 --- a/compiled/facebook-www/ReactDOM-dev.classic.js +++ b/compiled/facebook-www/ReactDOM-dev.classic.js @@ -5656,115 +5656,107 @@ function updateDOMProperties( setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } } -} - -function createElement(type, props, rootContainerElement, parentNamespace) { - var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML - // tags get no namespace. +} // Creates elements in the HTML namesapce - var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement); +function createHTMLElement(type, props, ownerDocument) { + var isCustomComponentTag; var domElement; - var namespaceURI = parentNamespace; - if (namespaceURI === HTML_NAMESPACE) { - namespaceURI = getIntrinsicNamespace(type); + { + isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to + // allow or . + + if (!isCustomComponentTag && type !== type.toLowerCase()) { + error( + "<%s /> is using incorrect casing. " + + "Use PascalCase for React components, " + + "or lowercase for HTML elements.", + type + ); + } } - if (namespaceURI === HTML_NAMESPACE) { - { - isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to - // allow or . + if (type === "script") { + // Create the script via .innerHTML so its "parser-inserted" flag is + // set to true and it does not execute + var div = ownerDocument.createElement("div"); - if (!isCustomComponentTag && type !== type.toLowerCase()) { + { + if (enableTrustedTypesIntegration && !didWarnScriptTags) { error( - "<%s /> is using incorrect casing. " + - "Use PascalCase for React components, " + - "or lowercase for HTML elements.", - type + "Encountered a script tag while rendering React component. " + + "Scripts inside React components are never executed when rendering " + + "on the client. Consider using template tag instead " + + "(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)." ); - } - } - if (type === "script") { - // Create the script via .innerHTML so its "parser-inserted" flag is - // set to true and it does not execute - var div = ownerDocument.createElement("div"); - - { - if (enableTrustedTypesIntegration && !didWarnScriptTags) { - error( - "Encountered a script tag while rendering React component. " + - "Scripts inside React components are never executed when rendering " + - "on the client. Consider using template tag instead " + - "(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)." - ); - - didWarnScriptTags = true; - } + didWarnScriptTags = true; } + } - div.innerHTML = " + // but it seems reasonable and conservative to reject this as a hydration error as well + return false; + } else if ( + instance.nodeName.toLowerCase() !== type.toLowerCase() || + isMarkedResource(instance) + ) { + // We are either about to + return true; + } else { + // We have an Element with the right type. + var element = instance; + var anyProps = props; // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if + // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension + // using high entropy attributes for certain types. This technique will fail for strange insertions like + // extension prepending
in the but that already breaks before and that is an edge case. + + switch (type) { + // case 'title': + //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope + // and if you are a HostComponent with type title we must either be in an context or this title must have an `itemProp` prop. + case "meta": { + // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be + // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags + // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime + // implications are minimal + if (!element.hasAttribute("itemprop")) { + // This is a Hoistable + return true; + } + + break; + } + + case "link": { + // Links come in many forms and we do expect 3rd parties to inject them into / . We exclude known resources + // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely + // matches. + var rel = element.getAttribute("rel"); + + if (rel === "stylesheet" && element.hasAttribute("data-precedence")) { + // This is a stylesheet resource + return true; + } else if ( + rel !== anyProps.rel || + element.getAttribute("href") !== + (anyProps.href == null ? null : anyProps.href) || + element.getAttribute("crossorigin") !== + (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) || + element.getAttribute("title") !== + (anyProps.title == null ? null : anyProps.title) + ) { + // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect + // and title could vary for rel alternate + return true; + } + + break; + } + + case "style": { + // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags + // in or are likely never going to be unmounted given their position in the document and the fact they likely hold global styles + if (element.hasAttribute("data-precedence")) { + // This is a style resource + return true; + } + + break; + } + + case "script": { + // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes + // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could + // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes. + // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later + // if we learn it is problematic + var srcAttr = element.getAttribute("src"); + + if ( + srcAttr && + element.hasAttribute("async") && + !element.hasAttribute("itemprop") + ) { + // This is an async script resource + return true; + } else if ( + srcAttr !== (anyProps.src == null ? null : anyProps.src) || + element.getAttribute("type") !== + (anyProps.type == null ? null : anyProps.type) || + element.getAttribute("crossorigin") !== + (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) + ) { + // This script is for a different src + return true; + } + + break; + } + } // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags, + // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic + // that should work in the vast majority of cases. + + return false; + } +} +function shouldSkipHydratableForTextInstance(instance) { + return instance.nodeType === ELEMENT_NODE; +} +function shouldSkipHydratableForSuspenseInstance(instance) { + return instance.nodeType === ELEMENT_NODE; +} function canHydrateInstance(instance, type, props) { if ( instance.nodeType !== ELEMENT_NODE || - type.toLowerCase() !== instance.nodeName.toLowerCase() + instance.nodeName.toLowerCase() !== type.toLowerCase() ) { return null; - } // This has now been refined to an element node. - - return instance; + } else { + return instance; + } } function canHydrateTextInstance(instance, text) { - if (text === "" || instance.nodeType !== TEXT_NODE) { + if (text === "") return null; + + if (instance.nodeType !== TEXT_NODE) { // Empty strings are not parsed by HTML so there won't be a correct match here. return null; } // This has now been refined to a text node. @@ -10986,7 +11099,6 @@ function canHydrateTextInstance(instance, text) { } function canHydrateSuspenseInstance(instance) { if (instance.nodeType !== COMMENT_NODE) { - // Empty strings are not parsed by HTML so there won't be a correct match here. return null; } // This has now been refined to a suspense node. @@ -11028,71 +11140,8 @@ function getNextHydratable(node) { for (; node != null; node = node.nextSibling) { var nodeType = node.nodeType; - { - if (nodeType === ELEMENT_NODE) { - var element = node; - - switch (element.tagName) { - // This is subtle. in SVG scope the title tag is case sensitive. we don't want to skip - // titles in svg but we do want to skip them outside of svg. there is an edge case where - // you could do `React.createElement('TITLE', ...)` inside an svg scope but the SSR serializer - // will still emit lowercase. Practically speaking the only time the DOM will have a non-uppercased - // title tagName is if it is inside an svg. - // Other Resource types like META, BASE, LINK, and SCRIPT should be treated as resources even inside - // svg scope because they are invalid otherwise. We still don't need to handle the lowercase variant - // because if they are present in the DOM already they would have been hoisted outside the SVG scope - // as Resources. So while it would be correct to skip a inside and this algorithm won't - // skip that link because the tagName will not be uppercased it functionally is irrelevant. If one - // tries to render incompatible types such as a non-resource stylesheet inside an svg the server will - // emit that invalid html and hydration will fail. In Dev this will present warnings guiding the - // developer on how to fix. - case "TITLE": - case "META": - case "HTML": - case "HEAD": - case "BODY": { - continue; - } - - case "LINK": { - var linkEl = element; // All links that are server rendered are resources except - // stylesheets that do not have a precedence - - if ( - linkEl.rel === "stylesheet" && - !linkEl.hasAttribute("data-precedence") - ) { - break; - } - - continue; - } - - case "STYLE": { - var styleEl = element; - - if (styleEl.hasAttribute("data-precedence")) { - continue; - } - - break; - } - - case "SCRIPT": { - var scriptEl = element; - - if (scriptEl.hasAttribute("async")) { - continue; - } - - break; - } - } - - break; - } else if (nodeType === TEXT_NODE) { - break; - } + if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) { + break; } if (nodeType === COMMENT_NODE) { @@ -11138,24 +11187,25 @@ function hydrateInstance( precacheFiberNode(internalInstanceHandle, instance); // TODO: Possibly defer this until the commit phase where all the events // get attached. - updateFiberProps(instance, props); + updateFiberProps(instance, props); // TODO: Temporary hack to check if we're in a concurrent root. We can delete + // when the legacy root API is removed. + + var isConcurrentMode = + (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; var parentNamespace; { var hostContextDev = hostContext; parentNamespace = hostContextDev.namespace; - } // TODO: Temporary hack to check if we're in a concurrent root. We can delete - // when the legacy root API is removed. + } - var isConcurrentMode = - (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; return diffHydratedProperties( instance, type, props, - parentNamespace, isConcurrentMode, - shouldWarnDev + shouldWarnDev, + parentNamespace ); } function hydrateTextInstance( @@ -11410,20 +11460,43 @@ function isHostHoistableType(type, props, hostContext) { outsideHostContainerContext = !hostContextDev.ancestorInfo.containerTagInScope; namespace = hostContextDev.namespace; + } // Global opt out of hoisting for anything in SVG Namespace or anything with an itemProp inside an itemScope + + if (namespace === SVG_NAMESPACE || props.itemProp != null) { + { + if ( + outsideHostContainerContext && + props.itemProp != null && + (type === "meta" || + type === "title" || + type === "style" || + type === "link" || + type === "script") + ) { + error( + "Cannot render a <%s> outside the main document if it has an `itemProp` prop. `itemProp` suggests the tag belongs to an" + + " `itemScope` which can appear anywhere in the DOM. If you were intending for React to hoist this <%s> remove the `itemProp` prop." + + " Otherwise, try moving this tag into the or of the Document.", + type, + type + ); + } + } + + return false; } switch (type) { case "meta": case "title": { - return namespace !== SVG_NAMESPACE; + return true; } case "style": { if ( typeof props.precedence !== "string" || typeof props.href !== "string" || - props.href === "" || - namespace === SVG_NAMESPACE + props.href === "" ) { { if (outsideHostContainerContext) { @@ -11450,8 +11523,7 @@ function isHostHoistableType(type, props, hostContext) { typeof props.href !== "string" || props.href === "" || props.onLoad || - props.onError || - namespace === SVG_NAMESPACE + props.onError ) { { if ( @@ -11515,8 +11587,7 @@ function isHostHoistableType(type, props, hostContext) { props.onLoad || props.onError || typeof props.src !== "string" || - !props.src || - namespace === SVG_NAMESPACE + !props.src ) { { if (outsideHostContainerContext) { @@ -11585,8 +11656,9 @@ function resolveSingletonInstance( validateDOMNestingDev ) { { + var hostContextDev = hostContext; + if (validateDOMNestingDev) { - var hostContextDev = hostContext; validateDOMNesting(type, null, hostContextDev.ancestorInfo); } } @@ -17307,6 +17379,7 @@ var isHydrating = false; // This flag allows for warning supression when we expe var didSuspendOrErrorDEV = false; // Hydration errors that were thrown inside this boundary var hydrationErrors = null; +var rootOrSingletonContext = false; function warnIfHydrating() { { @@ -17337,6 +17410,7 @@ function enterHydrationState(fiber) { isHydrating = true; hydrationErrors = null; didSuspendOrErrorDEV = false; + rootOrSingletonContext = true; return true; } @@ -17351,6 +17425,7 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( isHydrating = true; hydrationErrors = null; didSuspendOrErrorDEV = false; + rootOrSingletonContext = false; if (treeContext !== null) { restoreSuspendedTreeContext(fiber, treeContext); @@ -17523,70 +17598,64 @@ function insertNonHydratedInstance(returnFiber, fiber) { warnNonhydratedInstance(returnFiber, fiber); } -function tryHydrate(fiber, nextInstance) { - switch (fiber.tag) { - // HostSingleton is intentionally omitted. the hydration pathway for singletons is non-fallible - // you can find it inlined in claimHydratableSingleton - case HostComponent: { - var type = fiber.type; - var instance = canHydrateInstance(nextInstance, type); - - if (instance !== null) { - fiber.stateNode = instance; - hydrationParentFiber = fiber; - nextHydratableInstance = getFirstHydratableChild(instance); - return true; - } - - return false; - } +function tryHydrateInstance(fiber, nextInstance) { + // fiber is a HostComponent Fiber + var instance = canHydrateInstance(nextInstance, fiber.type); - case HostText: { - var text = fiber.pendingProps; - var textInstance = canHydrateTextInstance(nextInstance, text); + if (instance !== null) { + fiber.stateNode = instance; + hydrationParentFiber = fiber; + nextHydratableInstance = getFirstHydratableChild(instance); + rootOrSingletonContext = false; + return true; + } - if (textInstance !== null) { - fiber.stateNode = textInstance; - hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. + return false; +} - nextHydratableInstance = null; - return true; - } +function tryHydrateText(fiber, nextInstance) { + // fiber is a HostText Fiber + var text = fiber.pendingProps; + var textInstance = canHydrateTextInstance(nextInstance, text); - return false; - } + if (textInstance !== null) { + fiber.stateNode = textInstance; + hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. - case SuspenseComponent: { - var suspenseInstance = canHydrateSuspenseInstance(nextInstance); + nextHydratableInstance = null; + return true; + } - if (suspenseInstance !== null) { - var suspenseState = { - dehydrated: suspenseInstance, - treeContext: getSuspendedTreeContext(), - retryLane: OffscreenLane - }; - fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber. - // This simplifies the code for getHostSibling and deleting nodes, - // since it doesn't have to consider all Suspense boundaries and - // check if they're dehydrated ones or not. - - var dehydratedFragment = - createFiberFromDehydratedFragment(suspenseInstance); - dehydratedFragment.return = fiber; - fiber.child = dehydratedFragment; - hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into - // it during the first pass. Instead, we'll reenter it later. - - nextHydratableInstance = null; - return true; - } + return false; +} - return false; - } +function tryHydrateSuspense(fiber, nextInstance) { + // fiber is a SuspenseComponent Fiber + var suspenseInstance = canHydrateSuspenseInstance(nextInstance); - default: - return false; + if (suspenseInstance !== null) { + var suspenseState = { + dehydrated: suspenseInstance, + treeContext: getSuspendedTreeContext(), + retryLane: OffscreenLane + }; + fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber. + // This simplifies the code for getHostSibling and deleting nodes, + // since it doesn't have to consider all Suspense boundaries and + // check if they're dehydrated ones or not. + + var dehydratedFragment = + createFiberFromDehydratedFragment(suspenseInstance); + dehydratedFragment.return = fiber; + fiber.child = dehydratedFragment; + hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into + // it during the first pass. Instead, we'll reenter it later. + + nextHydratableInstance = null; + return true; } + + return false; } function shouldClientRenderOnMismatch(fiber) { @@ -17619,23 +17688,205 @@ function claimHydratableSingleton(fiber) { false )); hydrationParentFiber = fiber; + rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); } } +function advanceToFirstAttempableInstance(fiber) { + // fiber is HostComponent Fiber + while ( + nextHydratableInstance && + shouldSkipHydratableForInstance( + nextHydratableInstance, + fiber.type, + fiber.pendingProps + ) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + +function advanceToFirstAttempableTextInstance() { + while ( + nextHydratableInstance && + shouldSkipHydratableForTextInstance(nextHydratableInstance) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + +function advanceToFirstAttempableSuspenseInstance() { + while ( + nextHydratableInstance && + shouldSkipHydratableForSuspenseInstance(nextHydratableInstance) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + function tryToClaimNextHydratableInstance(fiber) { if (!isHydrating) { return; } - if (!isHydratable(fiber.type, fiber.pendingProps)) { - // This fiber never hydrates from the DOM and always does an insert - fiber.flags = (fiber.flags & ~Hydrating) | Placement; + { + if (!isHydratableType(fiber.type, fiber.pendingProps)) { + // This fiber never hydrates from the DOM and always does an insert + fiber.flags = (fiber.flags & ~Hydrating) | Placement; + isHydrating = false; + hydrationParentFiber = fiber; + return; + } + } + + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableInstance(fiber); + } + + var nextInstance = nextHydratableInstance; + + if (!nextInstance) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // Nothing to hydrate. Make it an insertion. + + insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; return; } + var firstAttemptedInstance = nextInstance; + + if (!tryHydrateInstance(fiber, nextInstance)) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // If we can't hydrate this instance let's try the next one. + // We use this as a heuristic. It's based on intuition and not data so it + // might be flawed or unnecessary. + + nextHydratableInstance = getNextHydratableSibling(nextInstance); + var prevHydrationParentFiber = hydrationParentFiber; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableInstance(fiber); + } + + if ( + !nextHydratableInstance || + !tryHydrateInstance(fiber, nextHydratableInstance) + ) { + // Nothing to hydrate. Make it an insertion. + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } // We matched the next one, we'll now assume that the first one was + // superfluous and we'll delete it. Since we can't eagerly delete it + // we'll have to schedule a deletion. To do that, this node needs a dummy + // fiber associated with it. + + deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + } +} + +function tryToClaimNextHydratableTextInstance(fiber) { + if (!isHydrating) { + return; + } + + var text = fiber.pendingProps; + var isHydratable = isHydratableText(text); + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext && isHydratable) { + // We may need to skip past certain nodes in these contexts. + // We don't skip if the text is not hydratable because we know no hydratables + // exist which could match this Fiber + advanceToFirstAttempableTextInstance(); + } + + var nextInstance = nextHydratableInstance; + + if (!nextInstance || !isHydratable) { + // We exclude non hydrabable text because we know there are no matching hydratables. + // We either throw or insert depending on the render mode. + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // Nothing to hydrate. Make it an insertion. + + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } + + var firstAttemptedInstance = nextInstance; + + if (!tryHydrateText(fiber, nextInstance)) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // If we can't hydrate this instance let's try the next one. + // We use this as a heuristic. It's based on intuition and not data so it + // might be flawed or unnecessary. + + nextHydratableInstance = getNextHydratableSibling(nextInstance); + var prevHydrationParentFiber = hydrationParentFiber; + + if (rootOrSingletonContext && isHydratable) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableTextInstance(); + } + + if ( + !nextHydratableInstance || + !tryHydrateText(fiber, nextHydratableInstance) + ) { + // Nothing to hydrate. Make it an insertion. + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } // We matched the next one, we'll now assume that the first one was + // superfluous and we'll delete it. Since we can't eagerly delete it + // we'll have to schedule a deletion. To do that, this node needs a dummy + // fiber associated with it. + + deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + } +} + +function tryToClaimNextHydratableSuspenseInstance(fiber) { + if (!isHydrating) { + return; + } + + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableSuspenseInstance(); + } + var nextInstance = nextHydratableInstance; if (!nextInstance) { @@ -17647,12 +17898,13 @@ function tryToClaimNextHydratableInstance(fiber) { insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; return; } var firstAttemptedInstance = nextInstance; - if (!tryHydrate(fiber, nextInstance)) { + if (!tryHydrateSuspense(fiber, nextInstance)) { if (shouldClientRenderOnMismatch(fiber)) { warnNonhydratedInstance(hydrationParentFiber, fiber); throwOnHydrationMismatch(); @@ -17660,14 +17912,23 @@ function tryToClaimNextHydratableInstance(fiber) { // We use this as a heuristic. It's based on intuition and not data so it // might be flawed or unnecessary. - nextInstance = getNextHydratableSibling(firstAttemptedInstance); + nextHydratableInstance = getNextHydratableSibling(nextInstance); var prevHydrationParentFiber = hydrationParentFiber; - if (!nextInstance || !tryHydrate(fiber, nextInstance)) { + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableSuspenseInstance(); + } + + if ( + !nextHydratableInstance || + !tryHydrateSuspense(fiber, nextHydratableInstance) + ) { // Nothing to hydrate. Make it an insertion. insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; return; } // We matched the next one, we'll now assume that the first one was // superfluous and we'll delete it. Since we can't eagerly delete it @@ -17784,19 +18045,24 @@ function skipPastDehydratedSuspenseInstance(fiber) { } function popToNextHostParent(fiber) { - var parent = fiber.return; + hydrationParentFiber = fiber.return; - while ( - parent !== null && - parent.tag !== HostComponent && - parent.tag !== HostRoot && - parent.tag !== SuspenseComponent && - parent.tag !== HostSingleton - ) { - parent = parent.return; - } + while (hydrationParentFiber) { + switch (hydrationParentFiber.tag) { + case HostRoot: + case HostSingleton: + rootOrSingletonContext = true; + return; + + case HostComponent: + case SuspenseComponent: + rootOrSingletonContext = false; + return; - hydrationParentFiber = parent; + default: + hydrationParentFiber = hydrationParentFiber.return; + } + } } function popHydrationState(fiber) { @@ -27866,7 +28132,7 @@ function updateHostSingleton(current, workInProgress, renderLanes) { function updateHostText$1(current, workInProgress) { if (current === null) { - tryToClaimNextHydratableInstance(workInProgress); + tryToClaimNextHydratableTextInstance(workInProgress); } // Nothing to do here. This is terminal. We'll do the completion step // immediately after. @@ -28345,7 +28611,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { pushFallbackTreeSuspenseHandler(workInProgress); } - tryToClaimNextHydratableInstance(workInProgress); // This could've been a dehydrated suspense component. + tryToClaimNextHydratableSuspenseInstance(workInProgress); // This could've been a dehydrated suspense component. var suspenseState = workInProgress.memoizedState; @@ -42124,7 +42390,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-classic-2fbfe042"; +var ReactVersion = "18.3.0-www-classic-f5d1deac"; function createPortal$1( children, diff --git a/compiled/facebook-www/ReactDOM-dev.modern.js b/compiled/facebook-www/ReactDOM-dev.modern.js index 207b9693240be..76469be785df0 100644 --- a/compiled/facebook-www/ReactDOM-dev.modern.js +++ b/compiled/facebook-www/ReactDOM-dev.modern.js @@ -12258,115 +12258,107 @@ function updateDOMProperties( setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } } -} - -function createElement(type, props, rootContainerElement, parentNamespace) { - var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML - // tags get no namespace. +} // Creates elements in the HTML namesapce - var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement); +function createHTMLElement(type, props, ownerDocument) { + var isCustomComponentTag; var domElement; - var namespaceURI = parentNamespace; - if (namespaceURI === HTML_NAMESPACE) { - namespaceURI = getIntrinsicNamespace(type); + { + isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to + // allow or . + + if (!isCustomComponentTag && type !== type.toLowerCase()) { + error( + "<%s /> is using incorrect casing. " + + "Use PascalCase for React components, " + + "or lowercase for HTML elements.", + type + ); + } } - if (namespaceURI === HTML_NAMESPACE) { - { - isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to - // allow or . + if (type === "script") { + // Create the script via .innerHTML so its "parser-inserted" flag is + // set to true and it does not execute + var div = ownerDocument.createElement("div"); - if (!isCustomComponentTag && type !== type.toLowerCase()) { + { + if (enableTrustedTypesIntegration && !didWarnScriptTags) { error( - "<%s /> is using incorrect casing. " + - "Use PascalCase for React components, " + - "or lowercase for HTML elements.", - type + "Encountered a script tag while rendering React component. " + + "Scripts inside React components are never executed when rendering " + + "on the client. Consider using template tag instead " + + "(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)." ); - } - } - - if (type === "script") { - // Create the script via .innerHTML so its "parser-inserted" flag is - // set to true and it does not execute - var div = ownerDocument.createElement("div"); - - { - if (enableTrustedTypesIntegration && !didWarnScriptTags) { - error( - "Encountered a script tag while rendering React component. " + - "Scripts inside React components are never executed when rendering " + - "on the client. Consider using template tag instead " + - "(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)." - ); - didWarnScriptTags = true; - } + didWarnScriptTags = true; } + } - div.innerHTML = " + // but it seems reasonable and conservative to reject this as a hydration error as well + return false; + } else if ( + instance.nodeName.toLowerCase() !== type.toLowerCase() || + isMarkedResource(instance) + ) { + // We are either about to + return true; + } else { + // We have an Element with the right type. + var element = instance; + var anyProps = props; // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if + // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension + // using high entropy attributes for certain types. This technique will fail for strange insertions like + // extension prepending
in the but that already breaks before and that is an edge case. + + switch (type) { + // case 'title': + //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope + // and if you are a HostComponent with type title we must either be in an context or this title must have an `itemProp` prop. + case "meta": { + // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be + // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags + // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime + // implications are minimal + if (!element.hasAttribute("itemprop")) { + // This is a Hoistable + return true; + } + + break; + } + + case "link": { + // Links come in many forms and we do expect 3rd parties to inject them into / . We exclude known resources + // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely + // matches. + var rel = element.getAttribute("rel"); + + if (rel === "stylesheet" && element.hasAttribute("data-precedence")) { + // This is a stylesheet resource + return true; + } else if ( + rel !== anyProps.rel || + element.getAttribute("href") !== + (anyProps.href == null ? null : anyProps.href) || + element.getAttribute("crossorigin") !== + (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) || + element.getAttribute("title") !== + (anyProps.title == null ? null : anyProps.title) + ) { + // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect + // and title could vary for rel alternate + return true; + } + + break; + } + + case "style": { + // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags + // in or are likely never going to be unmounted given their position in the document and the fact they likely hold global styles + if (element.hasAttribute("data-precedence")) { + // This is a style resource + return true; + } + + break; + } + + case "script": { + // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes + // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could + // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes. + // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later + // if we learn it is problematic + var srcAttr = element.getAttribute("src"); + + if ( + srcAttr && + element.hasAttribute("async") && + !element.hasAttribute("itemprop") + ) { + // This is an async script resource + return true; + } else if ( + srcAttr !== (anyProps.src == null ? null : anyProps.src) || + element.getAttribute("type") !== + (anyProps.type == null ? null : anyProps.type) || + element.getAttribute("crossorigin") !== + (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) + ) { + // This script is for a different src + return true; + } + + break; } + } // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags, + // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic + // that should work in the vast majority of cases. - return false; - } else if (type === "script") { - var async = props.async, - onLoad = props.onLoad, - onError = props.onError; - return !(async && (onLoad || onError)); - } - - return true; + return false; } } +function shouldSkipHydratableForTextInstance(instance) { + return instance.nodeType === ELEMENT_NODE; +} +function shouldSkipHydratableForSuspenseInstance(instance) { + return instance.nodeType === ELEMENT_NODE; +} function canHydrateInstance(instance, type, props) { if ( instance.nodeType !== ELEMENT_NODE || - type.toLowerCase() !== instance.nodeName.toLowerCase() + instance.nodeName.toLowerCase() !== type.toLowerCase() ) { return null; - } // This has now been refined to an element node. - - return instance; + } else { + return instance; + } } function canHydrateTextInstance(instance, text) { - if (text === "" || instance.nodeType !== TEXT_NODE) { + if (text === "") return null; + + if (instance.nodeType !== TEXT_NODE) { // Empty strings are not parsed by HTML so there won't be a correct match here. return null; } // This has now been refined to a text node. @@ -15637,7 +15750,6 @@ function canHydrateTextInstance(instance, text) { } function canHydrateSuspenseInstance(instance) { if (instance.nodeType !== COMMENT_NODE) { - // Empty strings are not parsed by HTML so there won't be a correct match here. return null; } // This has now been refined to a suspense node. @@ -15679,71 +15791,8 @@ function getNextHydratable(node) { for (; node != null; node = node.nextSibling) { var nodeType = node.nodeType; - { - if (nodeType === ELEMENT_NODE) { - var element = node; - - switch (element.tagName) { - // This is subtle. in SVG scope the title tag is case sensitive. we don't want to skip - // titles in svg but we do want to skip them outside of svg. there is an edge case where - // you could do `React.createElement('TITLE', ...)` inside an svg scope but the SSR serializer - // will still emit lowercase. Practically speaking the only time the DOM will have a non-uppercased - // title tagName is if it is inside an svg. - // Other Resource types like META, BASE, LINK, and SCRIPT should be treated as resources even inside - // svg scope because they are invalid otherwise. We still don't need to handle the lowercase variant - // because if they are present in the DOM already they would have been hoisted outside the SVG scope - // as Resources. So while it would be correct to skip a inside and this algorithm won't - // skip that link because the tagName will not be uppercased it functionally is irrelevant. If one - // tries to render incompatible types such as a non-resource stylesheet inside an svg the server will - // emit that invalid html and hydration will fail. In Dev this will present warnings guiding the - // developer on how to fix. - case "TITLE": - case "META": - case "HTML": - case "HEAD": - case "BODY": { - continue; - } - - case "LINK": { - var linkEl = element; // All links that are server rendered are resources except - // stylesheets that do not have a precedence - - if ( - linkEl.rel === "stylesheet" && - !linkEl.hasAttribute("data-precedence") - ) { - break; - } - - continue; - } - - case "STYLE": { - var styleEl = element; - - if (styleEl.hasAttribute("data-precedence")) { - continue; - } - - break; - } - - case "SCRIPT": { - var scriptEl = element; - - if (scriptEl.hasAttribute("async")) { - continue; - } - - break; - } - } - - break; - } else if (nodeType === TEXT_NODE) { - break; - } + if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) { + break; } if (nodeType === COMMENT_NODE) { @@ -15789,24 +15838,25 @@ function hydrateInstance( precacheFiberNode(internalInstanceHandle, instance); // TODO: Possibly defer this until the commit phase where all the events // get attached. - updateFiberProps(instance, props); + updateFiberProps(instance, props); // TODO: Temporary hack to check if we're in a concurrent root. We can delete + // when the legacy root API is removed. + + var isConcurrentMode = + (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; var parentNamespace; { var hostContextDev = hostContext; parentNamespace = hostContextDev.namespace; - } // TODO: Temporary hack to check if we're in a concurrent root. We can delete - // when the legacy root API is removed. + } - var isConcurrentMode = - (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; return diffHydratedProperties( instance, type, props, - parentNamespace, isConcurrentMode, - shouldWarnDev + shouldWarnDev, + parentNamespace ); } function hydrateTextInstance( @@ -16061,20 +16111,43 @@ function isHostHoistableType(type, props, hostContext) { outsideHostContainerContext = !hostContextDev.ancestorInfo.containerTagInScope; namespace = hostContextDev.namespace; + } // Global opt out of hoisting for anything in SVG Namespace or anything with an itemProp inside an itemScope + + if (namespace === SVG_NAMESPACE || props.itemProp != null) { + { + if ( + outsideHostContainerContext && + props.itemProp != null && + (type === "meta" || + type === "title" || + type === "style" || + type === "link" || + type === "script") + ) { + error( + "Cannot render a <%s> outside the main document if it has an `itemProp` prop. `itemProp` suggests the tag belongs to an" + + " `itemScope` which can appear anywhere in the DOM. If you were intending for React to hoist this <%s> remove the `itemProp` prop." + + " Otherwise, try moving this tag into the or of the Document.", + type, + type + ); + } + } + + return false; } switch (type) { case "meta": case "title": { - return namespace !== SVG_NAMESPACE; + return true; } case "style": { if ( typeof props.precedence !== "string" || typeof props.href !== "string" || - props.href === "" || - namespace === SVG_NAMESPACE + props.href === "" ) { { if (outsideHostContainerContext) { @@ -16101,8 +16174,7 @@ function isHostHoistableType(type, props, hostContext) { typeof props.href !== "string" || props.href === "" || props.onLoad || - props.onError || - namespace === SVG_NAMESPACE + props.onError ) { { if ( @@ -16166,8 +16238,7 @@ function isHostHoistableType(type, props, hostContext) { props.onLoad || props.onError || typeof props.src !== "string" || - !props.src || - namespace === SVG_NAMESPACE + !props.src ) { { if (outsideHostContainerContext) { @@ -16236,8 +16307,9 @@ function resolveSingletonInstance( validateDOMNestingDev ) { { + var hostContextDev = hostContext; + if (validateDOMNestingDev) { - var hostContextDev = hostContext; validateDOMNesting(type, null, hostContextDev.ancestorInfo); } } @@ -17007,6 +17079,7 @@ var isHydrating = false; // This flag allows for warning supression when we expe var didSuspendOrErrorDEV = false; // Hydration errors that were thrown inside this boundary var hydrationErrors = null; +var rootOrSingletonContext = false; function warnIfHydrating() { { @@ -17037,6 +17110,7 @@ function enterHydrationState(fiber) { isHydrating = true; hydrationErrors = null; didSuspendOrErrorDEV = false; + rootOrSingletonContext = true; return true; } @@ -17051,6 +17125,7 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( isHydrating = true; hydrationErrors = null; didSuspendOrErrorDEV = false; + rootOrSingletonContext = false; if (treeContext !== null) { restoreSuspendedTreeContext(fiber, treeContext); @@ -17223,70 +17298,64 @@ function insertNonHydratedInstance(returnFiber, fiber) { warnNonhydratedInstance(returnFiber, fiber); } -function tryHydrate(fiber, nextInstance) { - switch (fiber.tag) { - // HostSingleton is intentionally omitted. the hydration pathway for singletons is non-fallible - // you can find it inlined in claimHydratableSingleton - case HostComponent: { - var type = fiber.type; - var instance = canHydrateInstance(nextInstance, type); - - if (instance !== null) { - fiber.stateNode = instance; - hydrationParentFiber = fiber; - nextHydratableInstance = getFirstHydratableChild(instance); - return true; - } - - return false; - } +function tryHydrateInstance(fiber, nextInstance) { + // fiber is a HostComponent Fiber + var instance = canHydrateInstance(nextInstance, fiber.type); - case HostText: { - var text = fiber.pendingProps; - var textInstance = canHydrateTextInstance(nextInstance, text); + if (instance !== null) { + fiber.stateNode = instance; + hydrationParentFiber = fiber; + nextHydratableInstance = getFirstHydratableChild(instance); + rootOrSingletonContext = false; + return true; + } - if (textInstance !== null) { - fiber.stateNode = textInstance; - hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. + return false; +} - nextHydratableInstance = null; - return true; - } +function tryHydrateText(fiber, nextInstance) { + // fiber is a HostText Fiber + var text = fiber.pendingProps; + var textInstance = canHydrateTextInstance(nextInstance, text); - return false; - } + if (textInstance !== null) { + fiber.stateNode = textInstance; + hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. - case SuspenseComponent: { - var suspenseInstance = canHydrateSuspenseInstance(nextInstance); + nextHydratableInstance = null; + return true; + } - if (suspenseInstance !== null) { - var suspenseState = { - dehydrated: suspenseInstance, - treeContext: getSuspendedTreeContext(), - retryLane: OffscreenLane - }; - fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber. - // This simplifies the code for getHostSibling and deleting nodes, - // since it doesn't have to consider all Suspense boundaries and - // check if they're dehydrated ones or not. - - var dehydratedFragment = - createFiberFromDehydratedFragment(suspenseInstance); - dehydratedFragment.return = fiber; - fiber.child = dehydratedFragment; - hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into - // it during the first pass. Instead, we'll reenter it later. - - nextHydratableInstance = null; - return true; - } + return false; +} - return false; - } +function tryHydrateSuspense(fiber, nextInstance) { + // fiber is a SuspenseComponent Fiber + var suspenseInstance = canHydrateSuspenseInstance(nextInstance); - default: - return false; + if (suspenseInstance !== null) { + var suspenseState = { + dehydrated: suspenseInstance, + treeContext: getSuspendedTreeContext(), + retryLane: OffscreenLane + }; + fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber. + // This simplifies the code for getHostSibling and deleting nodes, + // since it doesn't have to consider all Suspense boundaries and + // check if they're dehydrated ones or not. + + var dehydratedFragment = + createFiberFromDehydratedFragment(suspenseInstance); + dehydratedFragment.return = fiber; + fiber.child = dehydratedFragment; + hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into + // it during the first pass. Instead, we'll reenter it later. + + nextHydratableInstance = null; + return true; } + + return false; } function shouldClientRenderOnMismatch(fiber) { @@ -17319,23 +17388,205 @@ function claimHydratableSingleton(fiber) { false )); hydrationParentFiber = fiber; + rootOrSingletonContext = true; nextHydratableInstance = getFirstHydratableChild(instance); } } +function advanceToFirstAttempableInstance(fiber) { + // fiber is HostComponent Fiber + while ( + nextHydratableInstance && + shouldSkipHydratableForInstance( + nextHydratableInstance, + fiber.type, + fiber.pendingProps + ) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + +function advanceToFirstAttempableTextInstance() { + while ( + nextHydratableInstance && + shouldSkipHydratableForTextInstance(nextHydratableInstance) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + +function advanceToFirstAttempableSuspenseInstance() { + while ( + nextHydratableInstance && + shouldSkipHydratableForSuspenseInstance(nextHydratableInstance) + ) { + // Flow doesn't understand that inside this block nextHydratableInstance is not null + var instance = nextHydratableInstance; + nextHydratableInstance = getNextHydratableSibling(instance); + } +} + function tryToClaimNextHydratableInstance(fiber) { if (!isHydrating) { return; } - if (!isHydratable(fiber.type, fiber.pendingProps)) { - // This fiber never hydrates from the DOM and always does an insert - fiber.flags = (fiber.flags & ~Hydrating) | Placement; + { + if (!isHydratableType(fiber.type, fiber.pendingProps)) { + // This fiber never hydrates from the DOM and always does an insert + fiber.flags = (fiber.flags & ~Hydrating) | Placement; + isHydrating = false; + hydrationParentFiber = fiber; + return; + } + } + + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableInstance(fiber); + } + + var nextInstance = nextHydratableInstance; + + if (!nextInstance) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // Nothing to hydrate. Make it an insertion. + + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } + + var firstAttemptedInstance = nextInstance; + + if (!tryHydrateInstance(fiber, nextInstance)) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // If we can't hydrate this instance let's try the next one. + // We use this as a heuristic. It's based on intuition and not data so it + // might be flawed or unnecessary. + + nextHydratableInstance = getNextHydratableSibling(nextInstance); + var prevHydrationParentFiber = hydrationParentFiber; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableInstance(fiber); + } + + if ( + !nextHydratableInstance || + !tryHydrateInstance(fiber, nextHydratableInstance) + ) { + // Nothing to hydrate. Make it an insertion. + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } // We matched the next one, we'll now assume that the first one was + // superfluous and we'll delete it. Since we can't eagerly delete it + // we'll have to schedule a deletion. To do that, this node needs a dummy + // fiber associated with it. + + deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + } +} + +function tryToClaimNextHydratableTextInstance(fiber) { + if (!isHydrating) { + return; + } + + var text = fiber.pendingProps; + var isHydratable = isHydratableText(text); + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext && isHydratable) { + // We may need to skip past certain nodes in these contexts. + // We don't skip if the text is not hydratable because we know no hydratables + // exist which could match this Fiber + advanceToFirstAttempableTextInstance(); + } + + var nextInstance = nextHydratableInstance; + + if (!nextInstance || !isHydratable) { + // We exclude non hydrabable text because we know there are no matching hydratables. + // We either throw or insert depending on the render mode. + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // Nothing to hydrate. Make it an insertion. + + insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } + + var firstAttemptedInstance = nextInstance; + + if (!tryHydrateText(fiber, nextInstance)) { + if (shouldClientRenderOnMismatch(fiber)) { + warnNonhydratedInstance(hydrationParentFiber, fiber); + throwOnHydrationMismatch(); + } // If we can't hydrate this instance let's try the next one. + // We use this as a heuristic. It's based on intuition and not data so it + // might be flawed or unnecessary. + + nextHydratableInstance = getNextHydratableSibling(nextInstance); + var prevHydrationParentFiber = hydrationParentFiber; + + if (rootOrSingletonContext && isHydratable) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableTextInstance(); + } + + if ( + !nextHydratableInstance || + !tryHydrateText(fiber, nextHydratableInstance) + ) { + // Nothing to hydrate. Make it an insertion. + insertNonHydratedInstance(hydrationParentFiber, fiber); + isHydrating = false; + hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; + return; + } // We matched the next one, we'll now assume that the first one was + // superfluous and we'll delete it. Since we can't eagerly delete it + // we'll have to schedule a deletion. To do that, this node needs a dummy + // fiber associated with it. + + deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); + } +} + +function tryToClaimNextHydratableSuspenseInstance(fiber) { + if (!isHydrating) { return; } + var initialInstance = nextHydratableInstance; + + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableSuspenseInstance(); + } + var nextInstance = nextHydratableInstance; if (!nextInstance) { @@ -17347,12 +17598,13 @@ function tryToClaimNextHydratableInstance(fiber) { insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; return; } var firstAttemptedInstance = nextInstance; - if (!tryHydrate(fiber, nextInstance)) { + if (!tryHydrateSuspense(fiber, nextInstance)) { if (shouldClientRenderOnMismatch(fiber)) { warnNonhydratedInstance(hydrationParentFiber, fiber); throwOnHydrationMismatch(); @@ -17360,14 +17612,23 @@ function tryToClaimNextHydratableInstance(fiber) { // We use this as a heuristic. It's based on intuition and not data so it // might be flawed or unnecessary. - nextInstance = getNextHydratableSibling(firstAttemptedInstance); + nextHydratableInstance = getNextHydratableSibling(nextInstance); var prevHydrationParentFiber = hydrationParentFiber; - if (!nextInstance || !tryHydrate(fiber, nextInstance)) { + if (rootOrSingletonContext) { + // We may need to skip past certain nodes in these contexts + advanceToFirstAttempableSuspenseInstance(); + } + + if ( + !nextHydratableInstance || + !tryHydrateSuspense(fiber, nextHydratableInstance) + ) { // Nothing to hydrate. Make it an insertion. insertNonHydratedInstance(hydrationParentFiber, fiber); isHydrating = false; hydrationParentFiber = fiber; + nextHydratableInstance = initialInstance; return; } // We matched the next one, we'll now assume that the first one was // superfluous and we'll delete it. Since we can't eagerly delete it @@ -17484,19 +17745,24 @@ function skipPastDehydratedSuspenseInstance(fiber) { } function popToNextHostParent(fiber) { - var parent = fiber.return; + hydrationParentFiber = fiber.return; - while ( - parent !== null && - parent.tag !== HostComponent && - parent.tag !== HostRoot && - parent.tag !== SuspenseComponent && - parent.tag !== HostSingleton - ) { - parent = parent.return; - } + while (hydrationParentFiber) { + switch (hydrationParentFiber.tag) { + case HostRoot: + case HostSingleton: + rootOrSingletonContext = true; + return; + + case HostComponent: + case SuspenseComponent: + rootOrSingletonContext = false; + return; - hydrationParentFiber = parent; + default: + hydrationParentFiber = hydrationParentFiber.return; + } + } } function popHydrationState(fiber) { @@ -27501,7 +27767,7 @@ function updateHostSingleton(current, workInProgress, renderLanes) { function updateHostText$1(current, workInProgress) { if (current === null) { - tryToClaimNextHydratableInstance(workInProgress); + tryToClaimNextHydratableTextInstance(workInProgress); } // Nothing to do here. This is terminal. We'll do the completion step // immediately after. @@ -27984,7 +28250,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) { pushFallbackTreeSuspenseHandler(workInProgress); } - tryToClaimNextHydratableInstance(workInProgress); // This could've been a dehydrated suspense component. + tryToClaimNextHydratableSuspenseInstance(workInProgress); // This could've been a dehydrated suspense component. var suspenseState = workInProgress.memoizedState; @@ -41728,7 +41994,7 @@ function createFiberRoot( return root; } -var ReactVersion = "18.3.0-www-modern-2190fcfa"; +var ReactVersion = "18.3.0-www-modern-20b8c9c5"; function createPortal$1( children, diff --git a/compiled/facebook-www/ReactDOM-prod.classic.js b/compiled/facebook-www/ReactDOM-prod.classic.js index 6006691fd7d01..470c6e3de34cf 100644 --- a/compiled/facebook-www/ReactDOM-prod.classic.js +++ b/compiled/facebook-www/ReactDOM-prod.classic.js @@ -1067,24 +1067,28 @@ function postMountWrapper(element) { null !== textContent && (element.value = textContent); } -function getIntrinsicNamespace(type) { - switch (type) { - case "svg": - return "http://www.w3.org/2000/svg"; - case "math": - return "http://www.w3.org/1998/Math/MathML"; - default: - return "http://www.w3.org/1999/xhtml"; - } -} function getChildNamespace(parentNamespace, type) { - return null == parentNamespace || + if ( + null == parentNamespace || "http://www.w3.org/1999/xhtml" === parentNamespace - ? getIntrinsicNamespace(type) - : "http://www.w3.org/2000/svg" === parentNamespace && + ) + a: switch (type) { + case "svg": + parentNamespace = "http://www.w3.org/2000/svg"; + break a; + case "math": + parentNamespace = "http://www.w3.org/1998/Math/MathML"; + break a; + default: + parentNamespace = "http://www.w3.org/1999/xhtml"; + } + else + parentNamespace = + "http://www.w3.org/2000/svg" === parentNamespace && "foreignObject" === type - ? "http://www.w3.org/1999/xhtml" - : parentNamespace; + ? "http://www.w3.org/1999/xhtml" + : parentNamespace; + return parentNamespace; } var reusableSVGContainer, setInnerHTML = (function (func) { @@ -1266,34 +1270,32 @@ function normalizeMarkupForTextOrAttribute(markup) { .replace(NORMALIZE_NEWLINES_REGEX, "\n") .replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, ""); } +function getOwnerDocumentFromRootContainer(rootContainerElement) { + return 9 === rootContainerElement.nodeType + ? rootContainerElement + : rootContainerElement.ownerDocument; +} function noop$1() {} -function createElement(type, props, rootContainerElement, parentNamespace) { - rootContainerElement = - 9 === rootContainerElement.nodeType - ? rootContainerElement - : rootContainerElement.ownerDocument; - "http://www.w3.org/1999/xhtml" === parentNamespace && - (parentNamespace = getIntrinsicNamespace(type)); - "http://www.w3.org/1999/xhtml" === parentNamespace - ? "script" === type - ? ((props = rootContainerElement.createElement("div")), - (props.innerHTML = "