From e091a0e461d083d024fc997d0357fc4bc7bd9d65 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 3 Oct 2022 17:06:39 -0700 Subject: [PATCH 1/7] track resources in different roots separately --- .../src/client/ReactDOMFloatClient.js | 102 +++++++++++------- .../src/client/ReactDOMHostConfig.js | 32 ++++-- .../src/__tests__/ReactDOMFloat-test.js | 47 ++++++++ .../src/ReactFiberHostContext.js | 21 ++++ .../src/ReactFiberHostContext.new.js | 6 ++ .../src/ReactFiberHostContext.old.js | 6 ++ scripts/error-codes/codes.json | 2 +- 7 files changed, 167 insertions(+), 49 deletions(-) create mode 100644 packages/react-reconciler/src/ReactFiberHostContext.js diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index 500cdfb79f9c8..3340c179f7b55 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -7,9 +7,10 @@ * @flow */ -import type {Instance} from './ReactDOMHostConfig'; +import type {Instance, Container} from './ReactDOMHostConfig'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js'; const {Dispatcher} = ReactDOMSharedInternals; +import {DOCUMENT_NODE} from '../shared/HTMLNodeType'; import { validateUnmatchedLinkResourceProps, validatePreloadResourceDifference, @@ -22,6 +23,7 @@ import { } from '../shared/ReactDOMResourceValidation'; import {createElement, setInitialProperties} from './ReactDOMComponent'; import {HTML_NAMESPACE} from '../shared/DOMNamespaces'; +import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; // The resource types we support. currently they match the form for the as argument. // In the future this may need to change, especially when modules / scripts are supported @@ -66,7 +68,7 @@ type StyleResource = { loaded: boolean, error: mixed, instance: ?Element, - ownerDocument: Document, + root: FloatRoot, }; type Props = {[string]: mixed}; @@ -79,11 +81,6 @@ type Resource = StyleResource | PreloadResource; // e = errored type StyleResourceLoadingState = Promise & {s?: 'l' | 'e'}; -// When rendering we set the currentDocument if one exists. we use this for Resources -// we encounter during render. If this is null and we are dispatching preloads and -// other calls on the ReactDOM module we look for the window global and get the document from there -let currentDocument: ?Document = null; - // It is valid to preload even when we aren't actively rendering. For cases where Float functions are // called when there is no rendering we track the last used document. It is not safe to insert // arbitrary resources into the lastCurrentDocument b/c it may not actually be the document @@ -93,14 +90,16 @@ let currentDocument: ?Document = null; let lastCurrentDocument: ?Document = null; let previousDispatcher = null; -export function prepareToRenderResources(ownerDocument: Document) { - currentDocument = lastCurrentDocument = ownerDocument; +export function prepareToRenderResources(rootContainer: Container) { + // $FlowFixMe all Container types are Node's and have a getRootNode method + const rootNode = rootContainer.getRootNode(); + lastCurrentDocument = getDocumentFromRoot(rootNode); + previousDispatcher = Dispatcher.current; Dispatcher.current = ReactDOMClientDispatcher; } export function cleanupAfterRenderResources() { - currentDocument = null; Dispatcher.current = previousDispatcher; previousDispatcher = null; } @@ -110,9 +109,18 @@ export function cleanupAfterRenderResources() { // from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one. export const ReactDOMClientDispatcher = {preload, preinit}; +type FloatDocument = Document & {_rstyles?: Map}; +type FloatShadowRoot = ShadowRoot & {_rstyles?: Map}; +type FloatRoot = FloatDocument | FloatShadowRoot; + // global maps of Resources const preloadResources: Map = new Map(); -const styleResources: Map = new Map(); + +function getCurrentResourceRoot(): null | FloatRoot { + const currentContainer = getCurrentRootHostContainer(); + // $FlowFixMe flow should know currentContainer is a Node and has getRootNode + return currentContainer ? currentContainer.getRootNode() : null; +} // Preloads are somewhat special. Even if we don't have the Document // used by the root that is rendering a component trying to insert a preload @@ -121,13 +129,22 @@ const styleResources: Map = new Map(); // lastCurrentDocument if that exists. As a fallback we will use the window.document // if available. function getDocumentForPreloads(): ?Document { - try { - return currentDocument || lastCurrentDocument || window.document; - } catch (error) { - return null; + const root = getCurrentResourceRoot(); + if (root) { + return root.ownerDocument || root; + } else { + try { + return lastCurrentDocument || window.document; + } catch (error) { + return null; + } } } +function getDocumentFromRoot(root: FloatRoot): Document { + return root.ownerDocument || root; +} + // -------------------------------------- // ReactDOM.Preload // -------------------------------------- @@ -200,8 +217,9 @@ function preinit(href: string, options: PreinitOptions) { typeof options === 'object' && options !== null ) { + const resourceRoot = getCurrentResourceRoot(); const as = options.as; - if (!currentDocument) { + if (!resourceRoot) { // We are going to emit a preload as a best effort fallback since this preinit // was called outside of a render. Given the passive nature of this fallback // we do not warn in dev when props disagree if there happens to already be a @@ -223,6 +241,10 @@ function preinit(href: string, options: PreinitOptions) { switch (as) { case 'style': { + let styleResources = resourceRoot._rstyles; + if (!styleResources) { + styleResources = resourceRoot._rstyles = new Map(); + } const precedence = options.precedence || 'default'; let resource = styleResources.get(href); if (resource) { @@ -241,8 +263,8 @@ function preinit(href: string, options: PreinitOptions) { options, ); resource = createStyleResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - currentDocument, + styleResources, + resourceRoot, href, precedence, resourceProps, @@ -303,9 +325,10 @@ export function getResource( pendingProps: Props, currentProps: null | Props, ): null | Resource { - if (!currentDocument) { + const resourceRoot = getCurrentResourceRoot(); + if (!resourceRoot) { throw new Error( - '"currentDocument" was expected to exist. This is a bug in React.', + '"resourceRoot" was expected to exist. This is a bug in React.', ); } switch (type) { @@ -313,6 +336,10 @@ export function getResource( const {rel} = pendingProps; switch (rel) { case 'stylesheet': { + let styleResources = resourceRoot._rstyles; + if (!styleResources) { + styleResources = resourceRoot._rstyles = new Map(); + } let didWarn; if (__DEV__) { if (currentProps) { @@ -348,8 +375,8 @@ export function getResource( } else { const resourceProps = stylePropsFromRawProps(styleRawProps); resource = createStyleResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - currentDocument, + styleResources, + resourceRoot, href, precedence, resourceProps, @@ -384,8 +411,7 @@ export function getResource( } else { const resourceProps = preloadPropsFromRawProps(preloadRawProps); resource = createPreloadResource( - // $FlowFixMe[incompatible-call] found when upgrading Flow - currentDocument, + getDocumentFromRoot(resourceRoot), href, resourceProps, ); @@ -463,7 +489,8 @@ function createResourceInstance( } function createStyleResource( - ownerDocument: Document, + styleResources: Map, + root: FloatRoot, href: string, precedence: string, props: StyleProps, @@ -479,7 +506,7 @@ function createStyleResource( const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes( href, ); - const existingEl = ownerDocument.querySelector( + const existingEl = root.querySelector( `link[rel="stylesheet"][href="${limitedEscapedHref}"]`, ); const resource = { @@ -492,7 +519,7 @@ function createStyleResource( preloaded: false, loaded: false, error: false, - ownerDocument, + root, instance: null, }; styleResources.set(href, resource); @@ -567,7 +594,7 @@ function immediatelyPreloadStyleResource(resource: StyleResource) { const {href, props} = resource; const preloadProps = preloadPropsFromStyleProps(props); resource.hint = createPreloadResource( - resource.ownerDocument, + getDocumentFromRoot(resource.root), href, preloadProps, ); @@ -613,11 +640,11 @@ function createPreloadResource( function acquireStyleResource(resource: StyleResource): Instance { if (!resource.instance) { - const {props, ownerDocument, precedence} = resource; + const {props, root, precedence} = resource; const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes( props.href, ); - const existingEl = ownerDocument.querySelector( + const existingEl = root.querySelector( `link[rel="stylesheet"][data-rprec][href="${limitedEscapedHref}"]`, ); if (existingEl) { @@ -649,11 +676,11 @@ function acquireStyleResource(resource: StyleResource): Instance { const instance = createResourceInstance( 'link', resource.props, - ownerDocument, + getDocumentFromRoot(root), ); attachLoadListeners(instance, resource); - insertStyleInstance(instance, precedence, ownerDocument); + insertStyleInstance(instance, precedence, root); resource.instance = instance; } } @@ -724,11 +751,9 @@ function onResourceError( function insertStyleInstance( instance: Instance, precedence: string, - ownerDocument: Document, + root: FloatRoot, ): void { - const nodes = ownerDocument.querySelectorAll( - 'link[rel="stylesheet"][data-rprec]', - ); + const nodes = root.querySelectorAll('link[rel="stylesheet"][data-rprec]'); const last = nodes.length ? nodes[nodes.length - 1] : null; let prior = last; for (let i = 0; i < nodes.length; i++) { @@ -746,9 +771,8 @@ function insertStyleInstance( // must exist. ((prior.parentNode: any): Node).insertBefore(instance, prior.nextSibling); } else { - // @TODO call getRootNode on root.container. if it is a Document, insert into head - // if it is a ShadowRoot insert it into the root node. - const parent = ownerDocument.head; + const parent = + root.nodeType === DOCUMENT_NODE ? ((root: any): Document).head : root; if (parent) { parent.insertBefore(instance, parent.firstChild); } else { diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js index 63c854a92acdd..294069fbe2e3f 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js @@ -41,7 +41,6 @@ import { warnForDeletedHydratableText, warnForInsertedHydratedElement, warnForInsertedHydratedText, - getOwnerDocumentFromRootContainer, } from './ReactDOMComponent'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; @@ -130,10 +129,15 @@ export type HydratableInstance = Instance | TextInstance | SuspenseInstance; export type PublicInstance = Element | Text; type HostContextDev = { namespace: string, + rootNode: Document | ShadowRoot, ancestorInfo: mixed, ... }; -type HostContextProd = string; +type HostContextProd = { + namespace: string, + rootNode: Document | ShadowRoot, + ... +}; export type HostContext = HostContextDev | HostContextProd; export type UpdatePayload = Array; export type ChildSet = void; // Unused @@ -185,12 +189,15 @@ export function getRootHostContext( break; } } + const rootNode = (((rootContainerInstance: Node).getRootNode(): any): + | Document + | ShadowRoot); if (__DEV__) { const validatedTag = type.toLowerCase(); const ancestorInfo = updatedAncestorInfo(null, validatedTag); - return {namespace, ancestorInfo}; + return {namespace, ancestorInfo, rootNode}; } - return namespace; + return {namespace, rootNode}; } export function getChildHostContext( @@ -204,10 +211,17 @@ export function getChildHostContext( parentHostContextDev.ancestorInfo, type, ); - return {namespace, ancestorInfo}; + return ({ + namespace, + ancestorInfo, + rootNode: parentHostContext.rootNode, + }: HostContextDev); } const parentNamespace = ((parentHostContext: any): HostContextProd); - return getChildNamespace(parentNamespace, type); + return { + namespace: getChildNamespace(parentNamespace.namespace, type), + rootNode: parentHostContext.rootNode, + }; } export function getPublicInstance(instance: Instance): Instance { @@ -279,7 +293,7 @@ export function createInstance( } parentNamespace = hostContextDev.namespace; } else { - parentNamespace = ((hostContext: any): HostContextProd); + parentNamespace = hostContext.namespace; } const domElement: Instance = createElement( type, @@ -854,7 +868,7 @@ export function hydrateInstance( const hostContextDev = ((hostContext: any): HostContextDev); parentNamespace = hostContextDev.namespace; } else { - parentNamespace = ((hostContext: any): HostContextProd); + parentNamespace = hostContext.namespace; } // TODO: Temporary hack to check if we're in a concurrent root. We can delete @@ -1376,7 +1390,7 @@ function isHostResourceInstance(instance: Instance | Container): boolean { export function prepareRendererToRender(rootContainer: Container) { if (enableFloat) { - prepareToRenderResources(getOwnerDocumentFromRootContainer(rootContainer)); + prepareToRenderResources(rootContainer); } } diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index 3484c172c4cf2..65926f6e15f0a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -552,6 +552,53 @@ describe('ReactDOMFloat', () => { }); }); + describe('document encapsulation', () => { + // @gate enableFloat + it('can support styles inside shadowRoots', async () => { + const shadow = document.body.attachShadow({mode: 'open'}); + const root = ReactDOMClient.createRoot(container); + root.render( + <> + + {ReactDOM.createPortal( +
+ + shadow +
, + shadow, + )} + container + , + ); + expect(Scheduler).toFlushWithoutYielding(); + expect(getVisibleChildren(document)).toEqual( + + + + + + +
container
+ + , + ); + expect(getVisibleChildren(shadow)).toEqual([ + , +
shadow
, + ]); + }); + }); + describe('style resources', () => { // @gate enableFloat it('treats link rel stylesheet elements as a style resource when it includes a precedence when server rendering', async () => { diff --git a/packages/react-reconciler/src/ReactFiberHostContext.js b/packages/react-reconciler/src/ReactFiberHostContext.js new file mode 100644 index 0000000000000..461ecdd492ab3 --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberHostContext.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Container} from './ReactFiberHostConfig'; +import {enableNewReconciler} from 'shared/ReactFeatureFlags'; + +import {getCurrentRootHostContainer as getCurrentRootHostContainer_old} from './ReactFiberHostContext.old'; + +import {getCurrentRootHostContainer as getCurrentRootHostContainer_new} from './ReactFiberHostContext.new'; + +export function getCurrentRootHostContainer(): null | Container { + return enableNewReconciler + ? getCurrentRootHostContainer_new() + : getCurrentRootHostContainer_old(); +} diff --git a/packages/react-reconciler/src/ReactFiberHostContext.new.js b/packages/react-reconciler/src/ReactFiberHostContext.new.js index 282414a658cba..1ea596222bfa5 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.new.js @@ -38,6 +38,11 @@ function requiredContext(c: Value | NoContextT): Value { return (c: any); } +function getCurrentRootHostContainer(): null | Container { + const container = rootInstanceStackCursor.current; + return container === NO_CONTEXT ? null : (container: any); +} + function getRootHostContainer(): Container { const rootInstance = requiredContext(rootInstanceStackCursor.current); return rootInstance; @@ -101,6 +106,7 @@ function popHostContext(fiber: Fiber): void { } export { + getCurrentRootHostContainer, getHostContext, getRootHostContainer, popHostContainer, diff --git a/packages/react-reconciler/src/ReactFiberHostContext.old.js b/packages/react-reconciler/src/ReactFiberHostContext.old.js index a4735b4bb6976..293c432e032fc 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.old.js @@ -38,6 +38,11 @@ function requiredContext(c: Value | NoContextT): Value { return (c: any); } +function getCurrentRootHostContainer(): null | Container { + const container = rootInstanceStackCursor.current; + return container === NO_CONTEXT ? null : (container: any); +} + function getRootHostContainer(): Container { const rootInstance = requiredContext(rootInstanceStackCursor.current); return rootInstance; @@ -101,6 +106,7 @@ function popHostContext(fiber: Fiber): void { } export { + getCurrentRootHostContainer, getHostContext, getRootHostContainer, popHostContainer, diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 1112e284460e0..48fcf61717273 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -431,6 +431,6 @@ "443": "acquireResource encountered a resource type it did not expect: \"%s\". this is a bug in React.", "444": "getResource encountered a resource type it did not expect: \"%s\". this is a bug in React.", "445": "\"currentResources\" was expected to exist. This is a bug in React.", - "446": "\"currentDocument\" was expected to exist. This is a bug in React.", + "446": "\"resourceRoot\" was expected to exist. This is a bug in React.", "447": "While attempting to insert a Resource, React expected the Document to contain a head element but it was not found." } From 97d294454401dd3dd9ca4f25a53f8d2a9fc0a9ee Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 13:56:55 -0700 Subject: [PATCH 2/7] flow types --- .../react-dom-bindings/src/client/ReactDOMFloatClient.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index 3340c179f7b55..ee004d5caf4e0 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -91,8 +91,9 @@ let lastCurrentDocument: ?Document = null; let previousDispatcher = null; export function prepareToRenderResources(rootContainer: Container) { - // $FlowFixMe all Container types are Node's and have a getRootNode method - const rootNode = rootContainer.getRootNode(); + // Flot thinks that getRootNode returns a Node but it actually returns a + // Document or ShadowRoot + const rootNode: FloatRoot = (rootContainer.getRootNode(): any); lastCurrentDocument = getDocumentFromRoot(rootNode); previousDispatcher = Dispatcher.current; From 20eb42f09836f2da09b013efcca465402082237c Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 14:13:40 -0700 Subject: [PATCH 3/7] add test demonstrating portals deep into shadowRoots --- .../src/__tests__/ReactDOMFloat-test.js | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index 65926f6e15f0a..6c45c68839a14 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -554,7 +554,7 @@ describe('ReactDOMFloat', () => { describe('document encapsulation', () => { // @gate enableFloat - it('can support styles inside shadowRoots', async () => { + it('can support styles inside portals to a shadowRoot', async () => { const shadow = document.body.attachShadow({mode: 'open'}); const root = ReactDOMClient.createRoot(container); root.render( @@ -597,6 +597,75 @@ describe('ReactDOMFloat', () => {
shadow
, ]); }); + // @gate enableFloat + it('can support styles inside portals to an element in shadowRoots', async () => { + const template = document.createElement('template'); + template.innerHTML = + "
"; + const shadow = document.body.attachShadow({mode: 'open'}); + shadow.appendChild(template.content); + + const shadowContainer1 = shadow.getElementById('shadowcontainer1'); + const shadowContainer2 = shadow.getElementById('shadowcontainer2'); + const root = ReactDOMClient.createRoot(container); + root.render( + <> + + {ReactDOM.createPortal( +
+ + 1 +
, + shadow, + )} + {ReactDOM.createPortal( +
+ + 2 +
, + shadowContainer1, + )} + {ReactDOM.createPortal( +
+ + 3 +
, + shadowContainer2, + )} + container + , + ); + expect(Scheduler).toFlushWithoutYielding(); + expect(getVisibleChildren(document)).toEqual( + + + + + + + + + +
container
+ + , + ); + expect(getVisibleChildren(shadow)).toEqual([ + , + , + , + , +
+
+
2
+
+
+
3
+
+
, +
1
, + ]); + }); }); describe('style resources', () => { From 2f4a34c27531bc36df29a7057fb3a262d2c95a92 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 14:19:42 -0700 Subject: [PATCH 4/7] revert hostcontext changes --- .../src/client/ReactDOMHostConfig.js | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js index 294069fbe2e3f..02089a0aae063 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js @@ -41,6 +41,7 @@ import { warnForDeletedHydratableText, warnForInsertedHydratedElement, warnForInsertedHydratedText, + getOwnerDocumentFromRootContainer, } from './ReactDOMComponent'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; @@ -129,15 +130,10 @@ export type HydratableInstance = Instance | TextInstance | SuspenseInstance; export type PublicInstance = Element | Text; type HostContextDev = { namespace: string, - rootNode: Document | ShadowRoot, ancestorInfo: mixed, ... }; -type HostContextProd = { - namespace: string, - rootNode: Document | ShadowRoot, - ... -}; +type HostContextProd = string; export type HostContext = HostContextDev | HostContextProd; export type UpdatePayload = Array; export type ChildSet = void; // Unused @@ -189,15 +185,12 @@ export function getRootHostContext( break; } } - const rootNode = (((rootContainerInstance: Node).getRootNode(): any): - | Document - | ShadowRoot); if (__DEV__) { const validatedTag = type.toLowerCase(); const ancestorInfo = updatedAncestorInfo(null, validatedTag); - return {namespace, ancestorInfo, rootNode}; + return {namespace, ancestorInfo}; } - return {namespace, rootNode}; + return namespace; } export function getChildHostContext( @@ -211,17 +204,10 @@ export function getChildHostContext( parentHostContextDev.ancestorInfo, type, ); - return ({ - namespace, - ancestorInfo, - rootNode: parentHostContext.rootNode, - }: HostContextDev); + return {namespace, ancestorInfo}; } const parentNamespace = ((parentHostContext: any): HostContextProd); - return { - namespace: getChildNamespace(parentNamespace.namespace, type), - rootNode: parentHostContext.rootNode, - }; + return getChildNamespace(parentNamespace, type); } export function getPublicInstance(instance: Instance): Instance { @@ -293,7 +279,7 @@ export function createInstance( } parentNamespace = hostContextDev.namespace; } else { - parentNamespace = hostContext.namespace; + parentNamespace = ((hostContext: any): HostContextProd); } const domElement: Instance = createElement( type, @@ -868,7 +854,7 @@ export function hydrateInstance( const hostContextDev = ((hostContext: any): HostContextDev); parentNamespace = hostContextDev.namespace; } else { - parentNamespace = hostContext.namespace; + parentNamespace = ((hostContext: any): HostContextProd); } // TODO: Temporary hack to check if we're in a concurrent root. We can delete From 9fb2f82ed80174cd3d7b0ad7a9510a610e4bcbfb Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 14:20:13 -0700 Subject: [PATCH 5/7] lints --- packages/react-dom-bindings/src/client/ReactDOMHostConfig.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js index 02089a0aae063..780fb59e571d5 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js @@ -41,7 +41,6 @@ import { warnForDeletedHydratableText, warnForInsertedHydratedElement, warnForInsertedHydratedText, - getOwnerDocumentFromRootContainer, } from './ReactDOMComponent'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; From b5170b2df3295498723b0cf90f71aaec0e459ab5 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 15:38:49 -0700 Subject: [PATCH 6/7] funge style cache key a la ReactDOMComponentTree --- .../src/client/ReactDOMFloatClient.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index ee004d5caf4e0..9a5c95109c989 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -110,9 +110,12 @@ export function cleanupAfterRenderResources() { // from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one. export const ReactDOMClientDispatcher = {preload, preinit}; -type FloatDocument = Document & {_rstyles?: Map}; -type FloatShadowRoot = ShadowRoot & {_rstyles?: Map}; -type FloatRoot = FloatDocument | FloatShadowRoot; +const randomKey = Math.random() + .toString(36) + .slice(2); +const stylesCacheKey = '__reactStyles$' + randomKey; + +type FloatRoot = Document | ShadowRoot; // global maps of Resources const preloadResources: Map = new Map(); @@ -146,6 +149,14 @@ function getDocumentFromRoot(root: FloatRoot): Document { return root.ownerDocument || root; } +function getStylesFromRoot(root: FloatRoot): Map { + let styles = (root: any)[stylesCacheKey]; + if (!styles) { + styles = (root: any)[stylesCacheKey] = new Map(); + } + return styles; +} + // -------------------------------------- // ReactDOM.Preload // -------------------------------------- @@ -242,10 +253,7 @@ function preinit(href: string, options: PreinitOptions) { switch (as) { case 'style': { - let styleResources = resourceRoot._rstyles; - if (!styleResources) { - styleResources = resourceRoot._rstyles = new Map(); - } + const styleResources = getStylesFromRoot(resourceRoot); const precedence = options.precedence || 'default'; let resource = styleResources.get(href); if (resource) { @@ -337,10 +345,7 @@ export function getResource( const {rel} = pendingProps; switch (rel) { case 'stylesheet': { - let styleResources = resourceRoot._rstyles; - if (!styleResources) { - styleResources = resourceRoot._rstyles = new Map(); - } + const styleResources = getStylesFromRoot(resourceRoot); let didWarn; if (__DEV__) { if (currentProps) { From ba369c82847b7df42ef96c79846a9705bfd795ec Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 4 Oct 2022 15:44:34 -0700 Subject: [PATCH 7/7] hide hacks in componentTree --- .../src/client/ReactDOMComponentTree.js | 10 ++++++++++ .../src/client/ReactDOMFloatClient.js | 19 ++++--------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js index 064646a7c1f91..5c45949f97419 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponentTree.js @@ -7,6 +7,7 @@ * @flow */ +import type {FloatRoot, StyleResource} from './ReactDOMFloatClient'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type {ReactScopeInstance} from 'shared/ReactTypes'; import type { @@ -42,6 +43,7 @@ const internalContainerInstanceKey = '__reactContainer$' + randomKey; const internalEventHandlersKey = '__reactEvents$' + randomKey; const internalEventHandlerListenersKey = '__reactListeners$' + randomKey; const internalEventHandlesSetKey = '__reactHandles$' + randomKey; +const internalRootNodeStylesSetKey = '__reactStyles$' + randomKey; export function detachDeletedInstance(node: Instance): void { // TODO: This function is only called on host components. I don't think all of @@ -266,3 +268,11 @@ export function doesTargetHaveEventHandle( } return eventHandles.has(eventHandle); } + +export function getStylesFromRoot(root: FloatRoot): Map { + let styles = (root: any)[internalRootNodeStylesSetKey]; + if (!styles) { + styles = (root: any)[internalRootNodeStylesSetKey] = new Map(); + } + return styles; +} diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index 9a5c95109c989..cf803261ab92f 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -8,6 +8,7 @@ */ import type {Instance, Container} from './ReactDOMHostConfig'; + import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js'; const {Dispatcher} = ReactDOMSharedInternals; import {DOCUMENT_NODE} from '../shared/HTMLNodeType'; @@ -22,6 +23,7 @@ import { validatePreinitArguments, } from '../shared/ReactDOMResourceValidation'; import {createElement, setInitialProperties} from './ReactDOMComponent'; +import {getStylesFromRoot} from './ReactDOMComponentTree'; import {HTML_NAMESPACE} from '../shared/DOMNamespaces'; import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; @@ -49,7 +51,7 @@ type StyleProps = { 'data-rprec': string, [string]: mixed, }; -type StyleResource = { +export type StyleResource = { type: 'style', // Ref count for resource @@ -110,12 +112,7 @@ export function cleanupAfterRenderResources() { // from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one. export const ReactDOMClientDispatcher = {preload, preinit}; -const randomKey = Math.random() - .toString(36) - .slice(2); -const stylesCacheKey = '__reactStyles$' + randomKey; - -type FloatRoot = Document | ShadowRoot; +export type FloatRoot = Document | ShadowRoot; // global maps of Resources const preloadResources: Map = new Map(); @@ -149,14 +146,6 @@ function getDocumentFromRoot(root: FloatRoot): Document { return root.ownerDocument || root; } -function getStylesFromRoot(root: FloatRoot): Map { - let styles = (root: any)[stylesCacheKey]; - if (!styles) { - styles = (root: any)[stylesCacheKey] = new Map(); - } - return styles; -} - // -------------------------------------- // ReactDOM.Preload // --------------------------------------