Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit a44e750

Browse files
authored
Store instance handles in an internal map behind flag (#35053)
We already append `randomKey` to each handle name to prevent external libraries from accessing and relying on these internals. But more libraries recently have been getting around this by simply iterating over the element properties and using a `startsWith` check. This flag allows us to experiment with moving these handles to an internal map. This PR starts with the two most common internals, the props object and the fiber. We can consider moving additional properties such as the container root and others depending on perf results.
1 parent 37b089a commit a44e750

8 files changed

Lines changed: 91 additions & 11 deletions

‎packages/react-dom-bindings/src/client/ReactDOMComponentTree.js‎

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {getParentHydrationBoundary} from './ReactFiberConfigDOM';
3838

3939
import{enableScopeAPI}from'shared/ReactFeatureFlags';
4040

41+
import{enableInternalInstanceMap}from'shared/ReactFeatureFlags';
42+
4143
constrandomKey=Math.random().toString(36).slice(2);
4244
constinternalInstanceKey='__reactFiber$'+randomKey;
4345
constinternalPropsKey='__reactProps$'+randomKey;
@@ -49,7 +51,32 @@ const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
4951
constinternalHoistableMarker='__reactMarker$'+randomKey;
5052
constinternalScrollTimer='__reactScroll$'+randomKey;
5153

54+
typeInstanceUnion=
55+
|Instance
56+
|TextInstance
57+
|SuspenseInstance
58+
|ActivityInstance
59+
|ReactScopeInstance
60+
|Container;
61+
62+
constPossiblyWeakMap=typeofWeakMap==='function' ? WeakMap : Map;
63+
constinternalInstanceMap:
64+
|WeakMap<InstanceUnion,Fiber>
65+
|Map<InstanceUnion,Fiber>=newPossiblyWeakMap();
66+
constinternalPropsMap:
67+
|WeakMap<InstanceUnion,Props>
68+
|Map<InstanceUnion,Props>=newPossiblyWeakMap();
69+
5270
exportfunctiondetachDeletedInstance(node: Instance): void{
71+
if(enableInternalInstanceMap){
72+
internalInstanceMap.delete(node);
73+
internalPropsMap.delete(node);
74+
delete(node: any)[internalEventHandlersKey];
75+
delete(node: any)[internalEventHandlerListenersKey];
76+
delete(node: any)[internalEventHandlesSetKey];
77+
delete(node: any)[internalRootNodeResourcesKey];
78+
return;
79+
}
5380
// TODO: This function is only called on host components. I don't think all of
5481
// these fields are relevant.
5582
delete(node: any)[internalInstanceKey];
@@ -68,6 +95,10 @@ export function precacheFiberNode(
6895
|ActivityInstance
6996
|ReactScopeInstance,
7097
): void{
98+
if(enableInternalInstanceMap){
99+
internalInstanceMap.set(node,hostInst);
100+
return;
101+
}
71102
(node: any)[internalInstanceKey]=hostInst;
72103
}
73104

@@ -95,7 +126,12 @@ export function isContainerMarkedAsRoot(node: Container): boolean {
95126
// HostRoot back. To get to the HostRoot, you need to pass a child of it.
96127
// The same thing applies to Suspense and Activity boundaries.
97128
export functiongetClosestInstanceFromNode(targetNode: Node): null|Fiber{
98-
lettargetInst=(targetNode: any)[internalInstanceKey];
129+
lettargetInst: void|Fiber;
130+
if(enableInternalInstanceMap){
131+
targetInst=internalInstanceMap.get(((targetNode: any): InstanceUnion));
132+
}else{
133+
targetInst=(targetNode: any)[internalInstanceKey];
134+
}
99135
if(targetInst){
100136
// Don't return HostRoot, SuspenseComponent or ActivityComponent here.
101137
returntargetInst;
@@ -112,9 +148,15 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
112148
// itself because the fibers are conceptually between the container
113149
// node and the first child. It isn't surrounding the container node.
114150
// If it's not a container, we check if it's an instance.
115-
targetInst=
116-
(parentNode: any)[internalContainerInstanceKey]||
117-
(parentNode: any)[internalInstanceKey];
151+
if(enableInternalInstanceMap){
152+
targetInst=
153+
(parentNode: any)[internalContainerInstanceKey]||
154+
internalInstanceMap.get(((parentNode: any): InstanceUnion));
155+
}else{
156+
targetInst=
157+
(parentNode: any)[internalContainerInstanceKey]||
158+
(parentNode: any)[internalInstanceKey];
159+
}
118160
if(targetInst){
119161
// Since this wasn't the direct target of the event, we might have
120162
// stepped past dehydrated DOM nodes to get here. However they could
@@ -147,8 +189,10 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
147189
// have had an internalInstanceKey on it.
148190
// Let's get the fiber associated with the SuspenseComponent
149191
// as the deepest instance.
150-
// $FlowFixMe[prop-missing]
151-
consttargetFiber=hydrationInstance[internalInstanceKey];
192+
consttargetFiber=enableInternalInstanceMap
193+
? internalInstanceMap.get(hydrationInstance)
194+
: // $FlowFixMe[prop-missing]
195+
hydrationInstance[internalInstanceKey];
152196
if(targetFiber){
153197
returntargetFiber;
154198
}
@@ -175,9 +219,16 @@ export function getClosestInstanceFromNode(targetNode: Node): null | Fiber {
175219
* instance, or null if the node was not rendered by this React.
176220
*/
177221
export functiongetInstanceFromNode(node: Node): Fiber|null{
178-
constinst=
179-
(node: any)[internalInstanceKey]||
180-
(node: any)[internalContainerInstanceKey];
222+
letinst: void|null|Fiber;
223+
if(enableInternalInstanceMap){
224+
inst=
225+
internalInstanceMap.get(((node: any): InstanceUnion))||
226+
(node: any)[internalContainerInstanceKey];
227+
}else{
228+
inst=
229+
(node: any)[internalInstanceKey]||
230+
(node: any)[internalContainerInstanceKey];
231+
}
181232
if(inst){
182233
consttag=inst.tag;
183234
if(
@@ -226,16 +277,24 @@ export function getFiberCurrentPropsFromNode(
226277
|TextInstance
227278
|SuspenseInstance
228279
|ActivityInstance,
229-
): Props{
280+
): Props|null{
281+
if(enableInternalInstanceMap){
282+
returninternalPropsMap.get(node)||null;
283+
}
230284
return(node: any)[internalPropsKey]||null;
231285
}
232286

233287
exportfunctionupdateFiberProps(node: Instance,props: Props): void{
288+
if(enableInternalInstanceMap){
289+
internalPropsMap.set(node,props);
290+
return;
291+
}
234292
(node: any)[internalPropsKey]=props;
235293
}
236294

237295
exportfunctiongetEventListenerSet(node: EventTarget): Set<string>{
238-
let elementListenerSet =(node: any)[internalEventHandlersKey];
296+
letelementListenerSet: Set<string>|void;
297+
elementListenerSet=(node: any)[internalEventHandlersKey];
239298
if(elementListenerSet===undefined){
240299
elementListenerSet =(node: any)[internalEventHandlersKey]=newSet();
241300
}
@@ -246,6 +305,9 @@ export function getFiberFromScopeInstance(
246305
scope: ReactScopeInstance,
247306
): null | Fiber {
248307
if(enableScopeAPI){
308+
if(enableInternalInstanceMap){
309+
returninternalInstanceMap.get(((scope: any): InstanceUnion))||null;
310+
}
249311
return(scope: any)[internalInstanceKey]||null;
250312
}
251313
return null;
@@ -318,6 +380,12 @@ export function clearScrollEndTimer(node: EventTarget): void {
318380
}
319381

320382
export function isOwnedInstance(node: Node): boolean {
383+
if(enableInternalInstanceMap){
384+
return!!(
385+
(node: any)[internalHoistableMarker]||
386+
internalInstanceMap.has((node: any))
387+
);
388+
}
321389
return !!(
322390
(node: any)[internalHoistableMarker] || (node: any)[internalInstanceKey]
323391
);

‎packages/shared/ReactFeatureFlags.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ export const enableFragmentRefs: boolean = true;
147147
exportconstenableFragmentRefsScrollIntoView: boolean=true;
148148
exportconstenableFragmentRefsInstanceHandles: boolean=false;
149149

150+
exportconstenableInternalInstanceMap: boolean=false;
151+
150152
// -----------------------------------------------------------------------------
151153
// Ready for next major.
152154
//

‎packages/shared/forks/ReactFeatureFlags.native-fb.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const enableComponentPerformanceTrack: boolean =
8484
__PROFILE__&&dynamicFlags.enableComponentPerformanceTrack;
8585
exportconstenablePerformanceIssueReporting: boolean=
8686
enableComponentPerformanceTrack;
87+
exportconstenableInternalInstanceMap: boolean=false;
8788

8889
// Flow magic to verify the exports of this file match the original version.
8990
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.native-oss.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const enableFragmentRefs: boolean = true;
7676
exportconstenableFragmentRefsScrollIntoView: boolean=false;
7777
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7878

79+
exportconstenableInternalInstanceMap: boolean=false;
80+
7981
// Profiling Only
8082
exportconstenableProfilerTimer: boolean=__PROFILE__;
8183
exportconstenableProfilerCommitHooks: boolean=__PROFILE__;

‎packages/shared/forks/ReactFeatureFlags.test-renderer.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export const enableFragmentRefs: boolean = true;
7777
exportconstenableFragmentRefsScrollIntoView: boolean=true;
7878
exportconstenableFragmentRefsInstanceHandles: boolean=false;
7979

80+
exportconstenableInternalInstanceMap: boolean=false;
81+
8082
// TODO: This must be in sync with the main ReactFeatureFlags file because
8183
// the Test Renderer's value must be the same as the one used by the
8284
// react package.

‎packages/shared/forks/ReactFeatureFlags.test-renderer.www.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ export const enableFragmentRefsScrollIntoView: boolean = false;
8585
exportconstenableFragmentRefsInstanceHandles: boolean=false;
8686
exportconstownerStackLimit=1e4;
8787

88+
exportconstenableInternalInstanceMap: boolean=false;
89+
8890
// Flow magic to verify the exports of this file match the original version.
8991
((((null: any): ExportsType): FeatureFlagsType): ExportsType);

‎packages/shared/forks/ReactFeatureFlags.www-dynamic.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export const enableFragmentRefs: boolean = __VARIANT__;
3838
exportconstenableFragmentRefsScrollIntoView: boolean=__VARIANT__;
3939
exportconstenableAsyncDebugInfo: boolean=__VARIANT__;
4040

41+
exportconstenableInternalInstanceMap: boolean=__VARIANT__;
42+
4143
// TODO: These flags are hard-coded to the default values used in open source.
4244
// Update the tests so that they pass in either mode, then set these
4345
// to __VARIANT__.

‎packages/shared/forks/ReactFeatureFlags.www.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const {
3535
enableFragmentRefs,
3636
enableFragmentRefsScrollIntoView,
3737
enableAsyncDebugInfo,
38+
enableInternalInstanceMap,
3839
}=dynamicFeatureFlags;
3940

4041
// On WWW, __EXPERIMENTAL__ is used for a new modern build.

0 commit comments

Comments
 (0)