From 978fae4b4f6d5aa28887b530b5c9bf28b1e7b74b Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 6 Mar 2023 19:52:35 -0800 Subject: [PATCH] [Float][Fiber] implement a faster hydration match for hoistable elements (#26154) This PR is now based on #26256 The original matching function for `hydrateHoistable` some challenging time complexity since we built up the list of matchable nodes for each link of that type and then had to check to exclusion. This new implementation aims to improve the complexity For hoisted title tags we match the first title if it is valid (not in SVG context and does not have `itemprop`, the two ways you opt out of hoisting when rendering titles). This path is much faster than others and we use it because valid Documents only have 1 title anyway and if we did have a mismatch the rendered title still ends up as the Document.title so there is no functional degradation for misses. For hoisted link and meta tags we track all potentially hydratable Elements of this type in a cache per Document. The cache is refreshed once each commit if and only if there is a title or meta hoistable hydrating. The caches are partitioned by a natural key for each type (href for link and content for meta). Then secondary attributes are checked to see if the potential match is matchable. For link we check `rel`, `title`, and `crossorigin`. These should provide enough entropy that we never have collisions except is contrived cases and even then it should not affect functionality of the page. This should also be tolerant of links being injected in arbitrary places in the Document by 3rd party scripts and browser extensions For meta we check `name`, `property`, `http-equiv`, and `charset`. These should provide enough entropy that we don't have meaningful collisions. It is concievable with og tags that there may be true duplciates `` but even if we did bind to the wrong instance meta tags are typically only read from SSR by bots and rarely inserted by 3rd parties so an adverse functional outcome is not expected. --- .../src/client/ReactDOMComponent.js | 140 ++++--- .../src/client/ReactDOMComponentTree.js | 16 +- .../src/client/ReactDOMFloatClient.js | 385 +++++++++--------- .../src/client/ReactDOMHostConfig.js | 43 +- .../src/__tests__/ReactDOMFloat-test.js | 122 +++++- .../src/ReactFiberCommitWork.js | 3 + .../ReactFiberHostConfigWithNoResources.js | 1 + .../src/ReactFiberHydrationContext.js | 18 +- .../src/forks/ReactFiberHostConfig.custom.js | 4 +- scripts/error-codes/codes.json | 3 +- 10 files changed, 433 insertions(+), 302 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index e736f2ffb5..0caaac1221 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -55,12 +55,7 @@ import { setValueForStyles, validateShorthandPropertyCollisionInDev, } from './CSSPropertyOperations'; -import { - HTML_NAMESPACE, - MATH_NAMESPACE, - SVG_NAMESPACE, - getIntrinsicNamespace, -} from '../shared/DOMNamespaces'; +import {HTML_NAMESPACE, getIntrinsicNamespace} from '../shared/DOMNamespaces'; import { getPropertyInfo, shouldIgnoreAttribute, @@ -380,15 +375,83 @@ function updateDOMProperties( } } +// creates a script element that won't execute +export function createPotentiallyInlineScriptElement( + ownerDocument: Document, +): Element { + // Create the script via .innerHTML so its "parser-inserted" flag is + // set to true and it does not execute + const div = ownerDocument.createElement('div'); + if (__DEV__) { + if (enableTrustedTypesIntegration && !didWarnScriptTags) { + console.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; + } + } + div.innerHTML = '