Skip to content

Commit

Permalink
Add Flag to Favor Hydration Performance over User Safety (#28655)
Browse files Browse the repository at this point in the history
If false, this ignores text comparison checks during hydration at the
risk of privacy safety.

Since React 18 we recreate the DOM starting from the nearest Suspense
boundary if any of the text content mismatches. This ensures that if we
have nodes that otherwise line up correctly such as if they're the same
type of Component but in a different order, then we don't accidentally
transfer state or attributes to the wrong one.

If we didn't do this e.g. attributes like image src might not line up
with the text. E.g. you might show the wrong profile picture with the
wrong name. However, the main reason we do this is because it's a
security/privacy concern if state from the original node can transfer to
the other one. For example if you start typing into a text field to
reply to a story but then it turns out that the hydration was in a
different order, you might submit that text into a different story than
you intended. Similarly, if you've already clicked an item and that gets
replayed using Action replaying or is synchronously force hydrated -
that click might end up applying to a different item in the list than
you intended. E.g. liking the wrong photo.

Unfortunately a common case where this happens is when Google Translate
is applied to a page. It'll always cause mismatches and recreate the
tree. Most of the time this wouldn't be visible to users because it'd
just recreate to the same thing and then translate again. It can affect
metrics that trace when this hydration happened though.

Meta can use this flag to decide if they favor this perf metric over the
risk to user privacy.

This is similar to the old enableClientRenderFallbackOnTextMismatch flag
except this flag doesn't patch up the text when there's a mismatch.
Because we don't have the patching anymore. The assumption is that it is
safe to ignore the safety concern because we assume it's a match and
therefore favoring not patching it will lead to better perf.

DiffTrain build for [5910eb3](5910eb3)
  • Loading branch information
sebmarkbage committed Mar 27, 2024
1 parent 1fc73fb commit b8ac8eb
Show file tree
Hide file tree
Showing 13 changed files with 155 additions and 169 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2ec2aaea98588178525f83495669e11e96815a00
5910eb34567a8699d1faa73b546baafd94f26411
4 changes: 2 additions & 2 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ if (__DEV__) {
return self;
}

var ReactVersion = "19.0.0-www-classic-be0d91b4";
var ReactVersion = "19.0.0-www-classic-17944549";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -193,7 +193,7 @@ if (__DEV__) {
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
var enableAsyncActions = true; // Logs additional User Timing API marks for use with an experimental profiling tool.
var enableAsyncActions = true;

var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;

Expand Down
4 changes: 2 additions & 2 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ if (__DEV__) {
return self;
}

var ReactVersion = "19.0.0-www-modern-71808898";
var ReactVersion = "19.0.0-www-modern-ac2705b5";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -193,7 +193,7 @@ if (__DEV__) {
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
var enableAsyncActions = true; // Logs additional User Timing API marks for use with an experimental profiling tool.
var enableAsyncActions = true;

var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;

Expand Down
9 changes: 5 additions & 4 deletions compiled/facebook-www/ReactDOM-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ if (__DEV__) {
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
var enableAsyncActions = true; // Logs additional User Timing API marks for use with an experimental profiling tool.
var enableAsyncActions = true;
var favorSafetyOverHydrationPerf = false; // Logs additional User Timing API marks for use with an experimental profiling tool.

var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;
var enableSuspenseCallback = true;
Expand Down Expand Up @@ -9212,7 +9213,7 @@ if (__DEV__) {
fiber
);

if (!didHydrate) {
if (!didHydrate && favorSafetyOverHydrationPerf) {
throwOnHydrationMismatch();
}
}
Expand Down Expand Up @@ -9279,7 +9280,7 @@ if (__DEV__) {
parentProps
);

if (!didHydrate) {
if (!didHydrate && favorSafetyOverHydrationPerf) {
throwOnHydrationMismatch();
}
}
Expand Down Expand Up @@ -36303,7 +36304,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "19.0.0-www-classic-441ec386";
var ReactVersion = "19.0.0-www-classic-8977f187";

function createPortal$1(
children,
Expand Down
9 changes: 5 additions & 4 deletions compiled/facebook-www/ReactDOM-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ if (__DEV__) {
var enableProfilerTimer = true;
var enableProfilerCommitHooks = true;
var enableProfilerNestedUpdatePhase = true;
var enableAsyncActions = true; // Logs additional User Timing API marks for use with an experimental profiling tool.
var enableAsyncActions = true;
var favorSafetyOverHydrationPerf = false; // Logs additional User Timing API marks for use with an experimental profiling tool.

var enableSchedulingProfiler = dynamicFeatureFlags.enableSchedulingProfiler;
var enableSuspenseCallback = true;
Expand Down Expand Up @@ -9174,7 +9175,7 @@ if (__DEV__) {
fiber
);

if (!didHydrate) {
if (!didHydrate && favorSafetyOverHydrationPerf) {
throwOnHydrationMismatch();
}
}
Expand Down Expand Up @@ -9241,7 +9242,7 @@ if (__DEV__) {
parentProps
);

if (!didHydrate) {
if (!didHydrate && favorSafetyOverHydrationPerf) {
throwOnHydrationMismatch();
}
}
Expand Down Expand Up @@ -36150,7 +36151,7 @@ if (__DEV__) {
return root;
}

var ReactVersion = "19.0.0-www-modern-3cd5f1d2";
var ReactVersion = "19.0.0-www-modern-b59f8aff";

function createPortal$1(
children,
Expand Down
51 changes: 24 additions & 27 deletions compiled/facebook-www/ReactDOM-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1763,19 +1763,20 @@ function prepareToHydrateHostInstance(fiber) {
track(instance);
}
fiber = props.children;
("string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)) &&
instance.textContent !== "" + fiber &&
!0 !== props.suppressHydrationWarning &&
!checkForUnmatchedText(instance.textContent, fiber)
? (instance = !1)
: (null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
if (
!(
"string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)
) ||
instance.textContent === "" + fiber ||
!0 === props.suppressHydrationWarning ||
checkForUnmatchedText(instance.textContent, fiber)
)
null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
null != props.onScrollEnd &&
listenToNonDelegatedEvent("scrollend", instance),
null != props.onClick && (instance.onclick = noop$2),
(instance = !0));
instance || throwOnHydrationMismatch();
null != props.onClick && (instance.onclick = noop$2);
}
function popToNextHostParent(fiber) {
for (hydrationParentFiber = fiber.return; hydrationParentFiber; )
Expand Down Expand Up @@ -8003,13 +8004,9 @@ function completeWork(current, workInProgress, renderLanes) {
newProps = currentResource.memoizedProps;
}
current[internalInstanceKey] = workInProgress;
current =
current.nodeValue === renderLanes ||
current.nodeValue === renderLanes ||
(null !== newProps && !0 === newProps.suppressHydrationWarning) ||
checkForUnmatchedText(current.nodeValue, renderLanes)
? !0
: !1;
current || throwOnHydrationMismatch();
checkForUnmatchedText(current.nodeValue, renderLanes);
} else
(current =
getOwnerDocumentFromRootContainer(current).createTextNode(
Expand Down Expand Up @@ -17080,10 +17077,10 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1759 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-classic-828721f2",
version: "19.0.0-www-classic-cb8dbb60",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2144 = {
var internals$jscomp$inline_2143 = {
bundleType: devToolsConfig$jscomp$inline_1759.bundleType,
version: devToolsConfig$jscomp$inline_1759.version,
rendererPackageName: devToolsConfig$jscomp$inline_1759.rendererPackageName,
Expand All @@ -17110,19 +17107,19 @@ var internals$jscomp$inline_2144 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-828721f2"
reconcilerVersion: "19.0.0-www-classic-cb8dbb60"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2145 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_2144 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_2145.isDisabled &&
hook$jscomp$inline_2145.supportsFiber
!hook$jscomp$inline_2144.isDisabled &&
hook$jscomp$inline_2144.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_2145.inject(
internals$jscomp$inline_2144
(rendererID = hook$jscomp$inline_2144.inject(
internals$jscomp$inline_2143
)),
(injectedHook = hook$jscomp$inline_2145);
(injectedHook = hook$jscomp$inline_2144);
} catch (err) {}
}
assign(Internals, {
Expand Down Expand Up @@ -17430,4 +17427,4 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactCurrentDispatcher$2.current.useHostTransitionStatus();
};
exports.version = "19.0.0-www-classic-828721f2";
exports.version = "19.0.0-www-classic-cb8dbb60";
51 changes: 24 additions & 27 deletions compiled/facebook-www/ReactDOM-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -1657,19 +1657,20 @@ function prepareToHydrateHostInstance(fiber) {
track(instance);
}
fiber = props.children;
("string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)) &&
instance.textContent !== "" + fiber &&
!0 !== props.suppressHydrationWarning &&
!checkForUnmatchedText(instance.textContent, fiber)
? (instance = !1)
: (null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
if (
!(
"string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)
) ||
instance.textContent === "" + fiber ||
!0 === props.suppressHydrationWarning ||
checkForUnmatchedText(instance.textContent, fiber)
)
null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
null != props.onScrollEnd &&
listenToNonDelegatedEvent("scrollend", instance),
null != props.onClick && (instance.onclick = noop$1),
(instance = !0));
instance || throwOnHydrationMismatch();
null != props.onClick && (instance.onclick = noop$1);
}
function popToNextHostParent(fiber) {
for (hydrationParentFiber = fiber.return; hydrationParentFiber; )
Expand Down Expand Up @@ -7816,13 +7817,9 @@ function completeWork(current, workInProgress, renderLanes) {
newProps = currentResource.memoizedProps;
}
current[internalInstanceKey] = workInProgress;
current =
current.nodeValue === renderLanes ||
current.nodeValue === renderLanes ||
(null !== newProps && !0 === newProps.suppressHydrationWarning) ||
checkForUnmatchedText(current.nodeValue, renderLanes)
? !0
: !1;
current || throwOnHydrationMismatch();
checkForUnmatchedText(current.nodeValue, renderLanes);
} else
(current =
getOwnerDocumentFromRootContainer(current).createTextNode(
Expand Down Expand Up @@ -16596,10 +16593,10 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1718 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-modern-cacf9b35",
version: "19.0.0-www-modern-9b12f53b",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2108 = {
var internals$jscomp$inline_2107 = {
bundleType: devToolsConfig$jscomp$inline_1718.bundleType,
version: devToolsConfig$jscomp$inline_1718.version,
rendererPackageName: devToolsConfig$jscomp$inline_1718.rendererPackageName,
Expand Down Expand Up @@ -16627,19 +16624,19 @@ var internals$jscomp$inline_2108 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-modern-cacf9b35"
reconcilerVersion: "19.0.0-www-modern-9b12f53b"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2109 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
var hook$jscomp$inline_2108 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (
!hook$jscomp$inline_2109.isDisabled &&
hook$jscomp$inline_2109.supportsFiber
!hook$jscomp$inline_2108.isDisabled &&
hook$jscomp$inline_2108.supportsFiber
)
try {
(rendererID = hook$jscomp$inline_2109.inject(
internals$jscomp$inline_2108
(rendererID = hook$jscomp$inline_2108.inject(
internals$jscomp$inline_2107
)),
(injectedHook = hook$jscomp$inline_2109);
(injectedHook = hook$jscomp$inline_2108);
} catch (err) {}
}
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = Internals;
Expand Down Expand Up @@ -16886,4 +16883,4 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactCurrentDispatcher$2.current.useHostTransitionStatus();
};
exports.version = "19.0.0-www-modern-cacf9b35";
exports.version = "19.0.0-www-modern-9b12f53b";
37 changes: 17 additions & 20 deletions compiled/facebook-www/ReactDOM-profiling.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1899,19 +1899,20 @@ function prepareToHydrateHostInstance(fiber) {
track(instance);
}
fiber = props.children;
("string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)) &&
instance.textContent !== "" + fiber &&
!0 !== props.suppressHydrationWarning &&
!checkForUnmatchedText(instance.textContent, fiber)
? (instance = !1)
: (null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
if (
!(
"string" === typeof fiber ||
"number" === typeof fiber ||
(enableBigIntSupport && "bigint" === typeof fiber)
) ||
instance.textContent === "" + fiber ||
!0 === props.suppressHydrationWarning ||
checkForUnmatchedText(instance.textContent, fiber)
)
null != props.onScroll && listenToNonDelegatedEvent("scroll", instance),
null != props.onScrollEnd &&
listenToNonDelegatedEvent("scrollend", instance),
null != props.onClick && (instance.onclick = noop$2),
(instance = !0));
instance || throwOnHydrationMismatch();
null != props.onClick && (instance.onclick = noop$2);
}
function popToNextHostParent(fiber) {
for (hydrationParentFiber = fiber.return; hydrationParentFiber; )
Expand Down Expand Up @@ -8291,13 +8292,9 @@ function completeWork(current, workInProgress, renderLanes) {
newProps = currentResource.memoizedProps;
}
current[internalInstanceKey] = workInProgress;
current =
current.nodeValue === renderLanes ||
current.nodeValue === renderLanes ||
(null !== newProps && !0 === newProps.suppressHydrationWarning) ||
checkForUnmatchedText(current.nodeValue, renderLanes)
? !0
: !1;
current || throwOnHydrationMismatch();
checkForUnmatchedText(current.nodeValue, renderLanes);
} else
(current =
getOwnerDocumentFromRootContainer(current).createTextNode(
Expand Down Expand Up @@ -17829,7 +17826,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1844 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "19.0.0-www-classic-56bf8085",
version: "19.0.0-www-classic-670af450",
rendererPackageName: "react-dom"
};
(function (internals) {
Expand Down Expand Up @@ -17873,7 +17870,7 @@ var devToolsConfig$jscomp$inline_1844 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-www-classic-56bf8085"
reconcilerVersion: "19.0.0-www-classic-670af450"
});
assign(Internals, {
ReactBrowserEventEmitter: {
Expand Down Expand Up @@ -18180,7 +18177,7 @@ exports.useFormState = function (action, initialState, permalink) {
exports.useFormStatus = function () {
return ReactCurrentDispatcher$2.current.useHostTransitionStatus();
};
exports.version = "19.0.0-www-classic-56bf8085";
exports.version = "19.0.0-www-classic-670af450";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
Loading

0 comments on commit b8ac8eb

Please sign in to comment.