Skip to content

Commit 6853d7a

Browse files
authored
[Perf Tracks] Prevent crash when accessing $$typeof (#35679)
1 parent e32c126 commit 6853d7a

File tree

2 files changed

+117
-3
lines changed

2 files changed

+117
-3
lines changed

packages/react-reconciler/src/__tests__/ReactPerformanceTrack-test.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,4 +523,110 @@ describe('ReactPerformanceTracks', () => {
523523
],
524524
]);
525525
});
526+
527+
// @gate __DEV__ && enableComponentPerformanceTrack
528+
it('diffs HTML-like objects', async () => {
529+
const App = function App({container}) {
530+
Scheduler.unstable_advanceTime(10);
531+
React.useEffect(() => {}, [container]);
532+
};
533+
534+
class Window {}
535+
const createOpaqueOriginWindow = () => {
536+
return new Proxy(new Window(), {
537+
get(target, prop) {
538+
if (prop === Symbol.toStringTag) {
539+
return target[Symbol.toStringTag];
540+
}
541+
// Some properties are allowed if JS itself is accessign those e.g.
542+
// Symbol.toStringTag.
543+
// Just make sure React isn't accessing arbitrary properties.
544+
throw new Error(
545+
`Failed to read named property '${String(prop)}' from Window`,
546+
);
547+
},
548+
});
549+
};
550+
551+
class OpaqueOriginHTMLIFrameElement {
552+
constructor(textContent) {
553+
this.textContent = textContent;
554+
}
555+
contentWindow = createOpaqueOriginWindow();
556+
nodeType = 1;
557+
[Symbol.toStringTag] = 'HTMLIFrameElement';
558+
}
559+
560+
Scheduler.unstable_advanceTime(1);
561+
await act(() => {
562+
ReactNoop.render(
563+
<App
564+
container={new OpaqueOriginHTMLIFrameElement('foo')}
565+
contentWindow={createOpaqueOriginWindow()}
566+
/>,
567+
);
568+
});
569+
570+
expect(performanceMeasureCalls).toEqual([
571+
[
572+
'Mount',
573+
{
574+
detail: {
575+
devtools: {
576+
color: 'warning',
577+
properties: null,
578+
tooltipText: 'Mount',
579+
track: 'Components ⚛',
580+
},
581+
},
582+
end: 11,
583+
start: 1,
584+
},
585+
],
586+
]);
587+
performanceMeasureCalls.length = 0;
588+
589+
Scheduler.unstable_advanceTime(10);
590+
591+
await act(() => {
592+
ReactNoop.render(
593+
<App
594+
container={new OpaqueOriginHTMLIFrameElement('bar')}
595+
contentWindow={createOpaqueOriginWindow()}
596+
/>,
597+
);
598+
});
599+
600+
expect(performanceMeasureCalls).toEqual([
601+
[
602+
'​App',
603+
{
604+
detail: {
605+
devtools: {
606+
color: 'primary-dark',
607+
properties: [
608+
['Changed Props', ''],
609+
['- container', 'HTMLIFrameElement'],
610+
['-   contentWindow', 'Window'],
611+
['-   nodeType', '1'],
612+
['-   textContent', '"foo"'],
613+
['+ container', 'HTMLIFrameElement'],
614+
['+   contentWindow', 'Window'],
615+
['+   nodeType', '1'],
616+
['+   textContent', '"bar"'],
617+
[
618+
'  contentWindow',
619+
'Referentially unequal but deeply equal objects. Consider memoization.',
620+
],
621+
],
622+
tooltipText: 'App',
623+
track: 'Components ⚛',
624+
},
625+
},
626+
end: 31,
627+
start: 21,
628+
},
629+
],
630+
]);
631+
});
526632
});

packages/shared/ReactPerformanceTrackProperties.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ export function addObjectToProperties(
8282
}
8383
}
8484

85+
function readReactElementTypeof(value: Object): mixed {
86+
// Prevents dotting into $$typeof in opaque origin windows.
87+
return '$$typeof' in value && hasOwnProperty.call(value, '$$typeof')
88+
? value.$$typeof
89+
: undefined;
90+
}
91+
8592
export function addValueToProperties(
8693
propertyName: string,
8794
value: mixed,
@@ -96,7 +103,7 @@ export function addValueToProperties(
96103
desc = 'null';
97104
break;
98105
} else {
99-
if (value.$$typeof === REACT_ELEMENT_TYPE) {
106+
if (readReactElementTypeof(value) === REACT_ELEMENT_TYPE) {
100107
// JSX
101108
const typeName = getComponentNameFromType(value.type) || '\u2026';
102109
const key = value.key;
@@ -352,9 +359,10 @@ export function addObjectDiffToProperties(
352359
typeof nextValue === 'object' &&
353360
prevValue !== null &&
354361
nextValue !== null &&
355-
prevValue.$$typeof === nextValue.$$typeof
362+
readReactElementTypeof(prevValue) ===
363+
readReactElementTypeof(nextValue)
356364
) {
357-
if (nextValue.$$typeof === REACT_ELEMENT_TYPE) {
365+
if (readReactElementTypeof(nextValue) === REACT_ELEMENT_TYPE) {
358366
if (
359367
prevValue.type === nextValue.type &&
360368
prevValue.key === nextValue.key

0 commit comments

Comments
 (0)