Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

track resources in different roots separately #25388

Merged
merged 7 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/react-dom-bindings/src/client/ReactDOMComponentTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -266,3 +268,11 @@ export function doesTargetHaveEventHandle(
}
return eventHandles.has(eventHandle);
}

export function getStylesFromRoot(root: FloatRoot): Map<string, StyleResource> {
let styles = (root: any)[internalRootNodeStylesSetKey];
if (!styles) {
styles = (root: any)[internalRootNodeStylesSetKey] = new Map();
}
return styles;
}
99 changes: 59 additions & 40 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
* @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,
Expand All @@ -21,7 +23,9 @@ 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';

// 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
Expand All @@ -47,7 +51,7 @@ type StyleProps = {
'data-rprec': string,
[string]: mixed,
};
type StyleResource = {
export type StyleResource = {
type: 'style',

// Ref count for resource
Expand All @@ -66,7 +70,7 @@ type StyleResource = {
loaded: boolean,
error: mixed,
instance: ?Element,
ownerDocument: Document,
root: FloatRoot,
};

type Props = {[string]: mixed};
Expand All @@ -79,11 +83,6 @@ type Resource = StyleResource | PreloadResource;
// e = errored
type StyleResourceLoadingState = Promise<mixed> & {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
Expand All @@ -93,14 +92,17 @@ let currentDocument: ?Document = null;
let lastCurrentDocument: ?Document = null;

let previousDispatcher = null;
export function prepareToRenderResources(ownerDocument: Document) {
currentDocument = lastCurrentDocument = ownerDocument;
export function prepareToRenderResources(rootContainer: Container) {
// 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;
Dispatcher.current = ReactDOMClientDispatcher;
}

export function cleanupAfterRenderResources() {
currentDocument = null;
Dispatcher.current = previousDispatcher;
previousDispatcher = null;
}
Expand All @@ -110,9 +112,16 @@ export function cleanupAfterRenderResources() {
// from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one.
export const ReactDOMClientDispatcher = {preload, preinit};

export type FloatRoot = Document | ShadowRoot;

// global maps of Resources
const preloadResources: Map<string, PreloadResource> = new Map();
const styleResources: Map<string, StyleResource> = 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
Expand All @@ -121,13 +130,22 @@ const styleResources: Map<string, StyleResource> = 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
// --------------------------------------
Expand Down Expand Up @@ -200,8 +218,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
Expand All @@ -223,6 +242,7 @@ function preinit(href: string, options: PreinitOptions) {

switch (as) {
case 'style': {
const styleResources = getStylesFromRoot(resourceRoot);
const precedence = options.precedence || 'default';
let resource = styleResources.get(href);
if (resource) {
Expand All @@ -241,8 +261,8 @@ function preinit(href: string, options: PreinitOptions) {
options,
);
resource = createStyleResource(
// $FlowFixMe[incompatible-call] found when upgrading Flow
currentDocument,
styleResources,
resourceRoot,
href,
precedence,
resourceProps,
Expand Down Expand Up @@ -303,16 +323,18 @@ 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) {
case 'link': {
const {rel} = pendingProps;
switch (rel) {
case 'stylesheet': {
const styleResources = getStylesFromRoot(resourceRoot);
let didWarn;
if (__DEV__) {
if (currentProps) {
Expand Down Expand Up @@ -348,8 +370,8 @@ export function getResource(
} else {
const resourceProps = stylePropsFromRawProps(styleRawProps);
resource = createStyleResource(
// $FlowFixMe[incompatible-call] found when upgrading Flow
currentDocument,
styleResources,
resourceRoot,
href,
precedence,
resourceProps,
Expand Down Expand Up @@ -384,8 +406,7 @@ export function getResource(
} else {
const resourceProps = preloadPropsFromRawProps(preloadRawProps);
resource = createPreloadResource(
// $FlowFixMe[incompatible-call] found when upgrading Flow
currentDocument,
getDocumentFromRoot(resourceRoot),
href,
resourceProps,
);
Expand Down Expand Up @@ -463,7 +484,8 @@ function createResourceInstance(
}

function createStyleResource(
ownerDocument: Document,
styleResources: Map<string, StyleResource>,
root: FloatRoot,
href: string,
precedence: string,
props: StyleProps,
Expand All @@ -479,7 +501,7 @@ function createStyleResource(
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
href,
);
const existingEl = ownerDocument.querySelector(
const existingEl = root.querySelector(
`link[rel="stylesheet"][href="${limitedEscapedHref}"]`,
);
const resource = {
Expand All @@ -492,7 +514,7 @@ function createStyleResource(
preloaded: false,
loaded: false,
error: false,
ownerDocument,
root,
instance: null,
};
styleResources.set(href, resource);
Expand Down Expand Up @@ -567,7 +589,7 @@ function immediatelyPreloadStyleResource(resource: StyleResource) {
const {href, props} = resource;
const preloadProps = preloadPropsFromStyleProps(props);
resource.hint = createPreloadResource(
resource.ownerDocument,
getDocumentFromRoot(resource.root),
href,
preloadProps,
);
Expand Down Expand Up @@ -613,11 +635,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) {
Expand Down Expand Up @@ -649,11 +671,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;
}
}
Expand Down Expand Up @@ -724,11 +746,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++) {
Expand All @@ -746,9 +766,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 {
Expand Down
3 changes: 1 addition & 2 deletions packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {
warnForDeletedHydratableText,
warnForInsertedHydratedElement,
warnForInsertedHydratedText,
getOwnerDocumentFromRootContainer,
} from './ReactDOMComponent';
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
import setTextContent from './setTextContent';
Expand Down Expand Up @@ -1376,7 +1375,7 @@ function isHostResourceInstance(instance: Instance | Container): boolean {

export function prepareRendererToRender(rootContainer: Container) {
if (enableFloat) {
prepareToRenderResources(getOwnerDocumentFromRootContainer(rootContainer));
prepareToRenderResources(rootContainer);
}
}

Expand Down
Loading