Skip to content

Commit 91b73fc

Browse files
committed
Allow Float methods to be called anytime in Fiber
Previously we restricted Float methods to only being callable while rendering. This allowed us to make associations between calls and their position in the DOM tree, for instance hoisting preinitialized styles into a ShadowRoot or an iframe Document. When considering how we are going to support Flight support in Float however it became clear that this restriction would lead to compromises on the implementation because the Flight client does not execute within the context of a client render. We want to be able to disaptch Float directives coming from Flight as soon as possible and this requires being able to call them outside of render. this patch modifies Float so that its methods are callable anywhere. The main consequence of this change is Float will always use the Document the renderer script is running within as the HoistableRoot. This means if you preinit as style inside a component render targeting a ShadowRoot the style will load in the ownerDocument not the ShadowRoot. Practially speaking it means that preinit is not useful inside ShadowRoots and iframes. This tradeoff was deemed acceptable because these methods are optimistic, not critical. Additionally, the other methods, preconntect, prefetchDNS, and preload, are not impacted because they already operated at the level of the ownerDocument and really only interface with the Network cache layer.
1 parent 019dea3 commit 91b73fc

File tree

4 files changed

+30
-55
lines changed

4 files changed

+30
-55
lines changed

packages/react-dom-bindings/src/client/ReactDOMHostConfig.js

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,24 +1939,9 @@ export function prepareToCommitHoistables() {
19391939
// that the resource is meant to apply too (for example stylesheets or scripts). This is only
19401940
// appropriate for resources that don't really have a strict tie to the document itself for example
19411941
// preloads
1942-
let lastCurrentDocument: ?Document = null;
1943-
let previousDispatcher = null;
1944-
export function prepareRendererToRender(rootContainer: Container) {
1945-
if (enableFloat) {
1946-
const rootNode = getHoistableRoot(rootContainer);
1947-
lastCurrentDocument = getDocumentFromRoot(rootNode);
1948-
1949-
previousDispatcher = Dispatcher.current;
1950-
Dispatcher.current = ReactDOMClientDispatcher;
1951-
}
1952-
}
1942+
export function prepareRendererToRender(rootContainer: Container) {}
19531943

1954-
export function resetRendererAfterRender() {
1955-
if (enableFloat) {
1956-
Dispatcher.current = previousDispatcher;
1957-
previousDispatcher = null;
1958-
}
1959-
}
1944+
export function resetRendererAfterRender() {}
19601945

19611946
// global collections of Resources
19621947
const preloadPropsMap: Map<string, PreloadProps> = new Map();
@@ -1979,29 +1964,12 @@ function getCurrentResourceRoot(): null | HoistableRoot {
19791964
return currentContainer ? getHoistableRoot(currentContainer) : null;
19801965
}
19811966

1982-
// Preloads are somewhat special. Even if we don't have the Document
1983-
// used by the root that is rendering a component trying to insert a preload
1984-
// we can still seed the file cache by doing the preload on any document we have
1985-
// access to. We prefer the currentDocument if it exists, we also prefer the
1986-
// lastCurrentDocument if that exists. As a fallback we will use the window.document
1987-
// if available.
1988-
function getDocumentForPreloads(): ?Document {
1989-
const root = getCurrentResourceRoot();
1990-
if (root) {
1991-
return root.ownerDocument || root;
1992-
} else {
1993-
try {
1994-
return lastCurrentDocument || window.document;
1995-
} catch (error) {
1996-
return null;
1997-
}
1998-
}
1999-
}
2000-
20011967
function getDocumentFromRoot(root: HoistableRoot): Document {
20021968
return root.ownerDocument || root;
20031969
}
20041970

1971+
let thisScriptOwnerDocument: null | Document = null;
1972+
20051973
// We want this to be the default dispatcher on ReactDOMSharedInternals but we don't want to mutate
20061974
// internals in Module scope. Instead we export it and Internals will import it. There is already a cycle
20071975
// from Internals -> ReactDOM -> HostConfig -> Internals so this doesn't introduce a new one.
@@ -2012,12 +1980,17 @@ export const ReactDOMClientDispatcher = {
20121980
preinit,
20131981
};
20141982

1983+
export function prepareHostDispatcher() {
1984+
Dispatcher.current = ReactDOMClientDispatcher;
1985+
thisScriptOwnerDocument = document;
1986+
}
1987+
20151988
function preconnectAs(
20161989
rel: 'preconnect' | 'dns-prefetch',
20171990
crossOrigin: null | '' | 'use-credentials',
20181991
href: string,
20191992
) {
2020-
const ownerDocument = getDocumentForPreloads();
1993+
const ownerDocument = thisScriptOwnerDocument;
20211994
if (typeof href === 'string' && href && ownerDocument) {
20221995
const limitedEscapedHref =
20231996
escapeSelectorAttributeValueInsideDoubleQuotes(href);
@@ -2040,6 +2013,9 @@ function preconnectAs(
20402013
}
20412014

20422015
function prefetchDNS(href: string, options?: mixed) {
2016+
if (!enableFloat) {
2017+
return;
2018+
}
20432019
if (__DEV__) {
20442020
if (typeof href !== 'string' || !href) {
20452021
console.error(
@@ -2067,6 +2043,9 @@ function prefetchDNS(href: string, options?: mixed) {
20672043
}
20682044

20692045
function preconnect(href: string, options?: {crossOrigin?: string}) {
2046+
if (!enableFloat) {
2047+
return;
2048+
}
20702049
if (__DEV__) {
20712050
if (typeof href !== 'string' || !href) {
20722051
console.error(
@@ -2102,10 +2081,13 @@ type PreloadOptions = {
21022081
type?: string,
21032082
};
21042083
function preload(href: string, options: PreloadOptions) {
2084+
if (!enableFloat) {
2085+
return;
2086+
}
21052087
if (__DEV__) {
21062088
validatePreloadArguments(href, options);
21072089
}
2108-
const ownerDocument = getDocumentForPreloads();
2090+
const ownerDocument = thisScriptOwnerDocument;
21092091
if (
21102092
typeof href === 'string' &&
21112093
href &&
@@ -2163,6 +2145,9 @@ type PreinitOptions = {
21632145
integrity?: string,
21642146
};
21652147
function preinit(href: string, options: PreinitOptions) {
2148+
if (!enableFloat) {
2149+
return;
2150+
}
21662151
if (__DEV__) {
21672152
validatePreinitArguments(href, options);
21682153
}
@@ -2181,7 +2166,7 @@ function preinit(href: string, options: PreinitOptions) {
21812166
// was called outside of a render. Given the passive nature of this fallback
21822167
// we do not warn in dev when props disagree if there happens to already be a
21832168
// matching preload with this href
2184-
const preloadDocument = getDocumentForPreloads();
2169+
const preloadDocument = thisScriptOwnerDocument;
21852170
if (preloadDocument) {
21862171
const limitedEscapedHref =
21872172
escapeSelectorAttributeValueInsideDoubleQuotes(href);

packages/react-dom/src/__tests__/ReactDOMFloat-test.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ let ReactDOMFizzServer;
2424
let Suspense;
2525
let textCache;
2626
let loadCache;
27-
let window;
28-
let document;
2927
let writable;
3028
const CSPnonce = null;
3129
let container;
@@ -51,8 +49,8 @@ function resetJSDOM(markup) {
5149
media: query,
5250
})),
5351
});
54-
window = jsdom.window;
55-
document = jsdom.window.document;
52+
global.window = jsdom.window;
53+
global.document = jsdom.window.document;
5654
}
5755

5856
describe('ReactDOMFloat', () => {

packages/react-dom/src/__tests__/ReactErrorLoggingRecovery-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('ReactErrorLoggingRecovery', () => {
5858
console.error = originalConsoleError;
5959
});
6060

61-
it('should recover from errors in console.error', function () {
61+
it('should recover from errors in console.error', async function () {
6262
const div = document.createElement('div');
6363
let didCatch = false;
6464
try {

packages/react-dom/src/client/ReactDOMRoot.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import type {
1313
TransitionTracingCallbacks,
1414
} from 'react-reconciler/src/ReactInternalTypes';
1515

16-
import ReactDOMSharedInternals from '../ReactDOMSharedInternals';
17-
const {Dispatcher} = ReactDOMSharedInternals;
18-
import {ReactDOMClientDispatcher} from 'react-dom-bindings/src/client/ReactDOMHostConfig';
16+
import {prepareHostDispatcher} from 'react-dom-bindings/src/client/ReactDOMHostConfig';
1917
import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying';
2018
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
2119
import {
@@ -247,11 +245,8 @@ export function createRoot(
247245
transitionCallbacks,
248246
);
249247
markContainerAsRoot(root.current, container);
248+
prepareHostDispatcher();
250249

251-
if (enableFloat) {
252-
// Set the default dispatcher to the client dispatcher
253-
Dispatcher.current = ReactDOMClientDispatcher;
254-
}
255250
const rootContainerElement: Document | Element | DocumentFragment =
256251
container.nodeType === COMMENT_NODE
257252
? (container.parentNode: any)
@@ -339,10 +334,7 @@ export function hydrateRoot(
339334
transitionCallbacks,
340335
);
341336
markContainerAsRoot(root.current, container);
342-
if (enableFloat) {
343-
// Set the default dispatcher to the client dispatcher
344-
Dispatcher.current = ReactDOMClientDispatcher;
345-
}
337+
prepareHostDispatcher();
346338
// This can't be a comment node since hydration doesn't work on comment nodes anyway.
347339
listenToAllSupportedEvents(container);
348340

0 commit comments

Comments
 (0)