[Perf Tracks] Don't crash on cross-origin Window props#36829
Open
junhyeong9812 wants to merge 1 commit into
Open
[Perf Tracks] Don't crash on cross-origin Window props#36829junhyeong9812 wants to merge 1 commit into
junhyeong9812 wants to merge 1 commit into
Conversation
The DEV component performance-track logger reads `$$typeof` and uses `key in obj` while diffing a component's props. For a cross-origin Window prop these accesses throw a SecurityError -- the `in` operator triggers [[HasProperty]], which is blocked for an opaque-origin Window -- and the throw escapes the commit phase (commitPassiveMountOnFiber) and corrupts the work-in-progress fiber, freezing the UI with "Should not already be working". Guard the `$$typeof` read in readReactElementTypeof and the `key in next` / `key in prev` diff checks through a new safeHas helper, treating an unreadable value as a non-element and an unreadable key as absent so the DEV-only logger never throws. A cross-origin Window also exposes child frames as enumerable numeric indices, so the diff's `in` checks are reached for those too.
daltino
approved these changes
Jun 20, 2026
daltino
left a comment
There was a problem hiding this comment.
This PR ensures React doesn’t break when interacting with cross-origin Window properties by adding proper safeguards to prevent SecurityError exceptions. The updates are well-contained, primarily adding test coverage and refining the logic for accessing such properties in a safe manner. Looks good to merge!
Author
|
Thank you so much for taking the time to review this — I really appreciate the kind feedback! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #36430.
In DEV builds, the component performance-track logger walks a component's props
on every update (
logComponentRender→addObjectDiffToProperties) and readsvalue.$$typeofto detect React elements. When a prop is a cross-originWindow— e.g. thecontentWindowof an<iframe srcdoc="">, whose origin is"null"— any property access throws aSecurityError. Because the logger runsinside the commit phase (
commitPassiveMountOnFiber), the throw escapes thecommit, corrupts the work-in-progress fiber, and every later render fails with
Should not already be working, freezing the whole UI. Production builds areunaffected (the logger is DEV-only).
readReactElementTypeofalready carried a comment about "opaque origin windows",but its guard was
'$$typeof' in value— and theinoperator itself triggers[[HasProperty]]→[[GetOwnProperty]], which throws for a cross-origin Window,so the guard never actually held.
The DEV logger must never throw while iterating props, so this guards the two
property accesses that can reach a cross-origin Window:
readReactElementTypeofwraps the$$typeofaccess (including theincheck) in
try/catch, returningundefined("not a React element") on throw.safeHas(new) guards the prop diff'skey in next/key in prev. Across-origin Window exposes its child frames as enumerable numeric indices,
so
for...incan yield"0", and theincheck then throws for an index thatexists on only one side of the diff. An unreadable key is treated as absent.
Both paths degrade gracefully — an unreadable value is logged as a plain object
or a removed/added key instead of crashing.
How did you test this change?
Added two regression tests in
ReactPerformanceTrack-test.jsthat model across-origin
Windowfaithfully with aProxy(throws onget/has/getOwnPropertyDescriptorfor everything except the symbols the engine itselfprobes;
nullprototype). The existing fixture only trappedget, so'$$typeof' in valueslipped through and never reproduced the crash.does not crash diffing cross-origin Window props (opaque origin)— achildless Window (covers the
readReactElementTypeofguard).does not crash diffing cross-origin Windows that contain child frames— aWindow exposing an enumerable child-frame index (covers the
safeHasguard).Both tests crash before this change and pass after.
yarn test packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js→ 8/8 pass (the 6 existing perf-track tests still pass).
yarn test --prodof the same file → 8/8 (the tests are@gated to DEV +enableComponentPerformanceTrack, so they are correctly gated out in prod).yarn linc,yarn prettier-check, andyarn flow dom-nodepass.