Stop creating Owner Stacks if many have been created recently (#32529)

Co-authored-by: Jack Pope <jackpope1@gmail.com>
This commit is contained in:
Sebastian "Sebbie" Silbermann 2025-03-23 15:47:03 -07:00 committed by GitHub
parent da996a15be
commit 4a9df08157
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 10835 additions and 38 deletions

23
fixtures/owner-stacks/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `yarn build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View File

@ -0,0 +1,36 @@
{
"name": "owner-stacks",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "experimental",
"react-dom": "experimental",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.0"
},
"scripts": {
"prestart": "cp -a ../../build/oss-experimental/. node_modules",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,62 @@
.App-header {
background-color: #282c34;
min-height: 10vh;
display: flex;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.page {
display: flex;
height: 90vh;
}
.content {
flex: 4;
background-color: #f0f0f0;
padding: 20px;
display: flex;
flex-direction: column;
text-align: left;
}
.content.highlight {
background-color: yellow;
}
.sidebar {
flex: 1;
background-color: #d0d0d0;
padding: 20px;
}
.line-number {
display: inline-block;
width: 30px;
text-align: right;
margin-right: 10px;
color: #888;
}
.text-symbol {
position: relative;
display: inline-block;
}
.hovercard {
display: none;
position: absolute;
top: -40px;
left: 0;
background-color: white;
border: 1px solid #ccc;
padding: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.text-symbol:hover .hovercard {
display: block;
}

View File

@ -0,0 +1,143 @@
import {useState} from 'react';
import {flushSync} from 'react-dom';
import './App.css';
const text = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
`;
const loremIpsum = text + text;
function TextSymbol({char, highlight}) {
const randomColor = highlight
? `#${Math.floor(Math.random() * 16777215).toString(16)}`
: 'transparent';
return (
<span className="text-symbol" style={{backgroundColor: randomColor}}>
{char}
<div className="hovercard">
<p>Character: {char}</p>
<p>Color: {randomColor}</p>
</div>
</span>
);
}
function TextLine({sentence, highlight, lineNumber}) {
const randomColor = highlight
? `#${Math.floor(Math.random() * 16777215).toString(16)}`
: 'transparent';
return (
<div style={{backgroundColor: randomColor}}>
<span className="line-number">{lineNumber}</span>
{sentence.split('').map((char, index) => (
<TextSymbol key={index} char={char} highlight={highlight} />
))}
</div>
);
}
function App() {
const [highlight, setHighlight] = useState(false);
const toggleHighlight = () => {
console.time('toggleHighlight');
flushSync(() => {
setHighlight(!highlight);
});
console.timeEnd('toggleHighlight');
};
return (
<div className="App">
<header className="App-header">Owner Stacks Stress Test</header>
<div className="page">
<div className={`content ${highlight ? 'highlight' : ''}`}>
{loremIpsum
.trim()
.split('\n')
.map((sentence, index) => (
<TextLine
key={index}
sentence={sentence.trim()}
highlight={highlight}
lineNumber={index + 1}
/>
))}
</div>
<div className="sidebar">
<button onClick={toggleHighlight}>
{highlight ? 'Remove Highlight' : 'Highlight Content'}
</button>
</div>
</div>
</div>
);
}
export default App;

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

File diff suppressed because it is too large Load Diff

View File

@ -315,7 +315,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(greeting)).toEqual( expect(getDebugInfo(greeting)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 11}, {time: 12},
{ {
name: 'Greeting', name: 'Greeting',
env: 'Server', env: 'Server',
@ -327,7 +327,7 @@ describe('ReactFlight', () => {
lastName: 'Smith', lastName: 'Smith',
}, },
}, },
{time: 12}, {time: 13},
] ]
: undefined, : undefined,
); );
@ -359,7 +359,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(promise)).toEqual( expect(getDebugInfo(promise)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 11}, {time: 12},
{ {
name: 'Greeting', name: 'Greeting',
env: 'Server', env: 'Server',
@ -371,7 +371,7 @@ describe('ReactFlight', () => {
lastName: 'Smith', lastName: 'Smith',
}, },
}, },
{time: 12}, {time: 13},
] ]
: undefined, : undefined,
); );
@ -2807,7 +2807,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(promise)).toEqual( expect(getDebugInfo(promise)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 18}, {time: 20},
{ {
name: 'ServerComponent', name: 'ServerComponent',
env: 'Server', env: 'Server',
@ -2818,7 +2818,7 @@ describe('ReactFlight', () => {
transport: expect.arrayContaining([]), transport: expect.arrayContaining([]),
}, },
}, },
{time: 19}, {time: 21},
] ]
: undefined, : undefined,
); );
@ -2829,7 +2829,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(thirdPartyChildren[0])).toEqual( expect(getDebugInfo(thirdPartyChildren[0])).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 13}, {time: 14},
{ {
name: 'ThirdPartyComponent', name: 'ThirdPartyComponent',
env: 'third-party', env: 'third-party',
@ -2838,15 +2838,15 @@ describe('ReactFlight', () => {
stack: ' in Object.<anonymous> (at **)', stack: ' in Object.<anonymous> (at **)',
props: {}, props: {},
}, },
{time: 14}, {time: 15},
{time: 21}, // This last one is when the promise resolved into the first party. {time: 23}, // This last one is when the promise resolved into the first party.
] ]
: undefined, : undefined,
); );
expect(getDebugInfo(thirdPartyChildren[1])).toEqual( expect(getDebugInfo(thirdPartyChildren[1])).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 15}, {time: 16},
{ {
name: 'ThirdPartyLazyComponent', name: 'ThirdPartyLazyComponent',
env: 'third-party', env: 'third-party',
@ -2855,14 +2855,14 @@ describe('ReactFlight', () => {
stack: ' in myLazy (at **)\n in lazyInitializer (at **)', stack: ' in myLazy (at **)\n in lazyInitializer (at **)',
props: {}, props: {},
}, },
{time: 16}, {time: 17},
] ]
: undefined, : undefined,
); );
expect(getDebugInfo(thirdPartyChildren[2])).toEqual( expect(getDebugInfo(thirdPartyChildren[2])).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 11}, {time: 12},
{ {
name: 'ThirdPartyFragmentComponent', name: 'ThirdPartyFragmentComponent',
env: 'third-party', env: 'third-party',
@ -2871,7 +2871,7 @@ describe('ReactFlight', () => {
stack: ' in Object.<anonymous> (at **)', stack: ' in Object.<anonymous> (at **)',
props: {}, props: {},
}, },
{time: 12}, {time: 13},
] ]
: undefined, : undefined,
); );
@ -2936,7 +2936,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(promise)).toEqual( expect(getDebugInfo(promise)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 14}, {time: 16},
{ {
name: 'ServerComponent', name: 'ServerComponent',
env: 'Server', env: 'Server',
@ -2947,7 +2947,7 @@ describe('ReactFlight', () => {
transport: expect.arrayContaining([]), transport: expect.arrayContaining([]),
}, },
}, },
{time: 15}, {time: 17},
] ]
: undefined, : undefined,
); );
@ -2956,7 +2956,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(thirdPartyFragment)).toEqual( expect(getDebugInfo(thirdPartyFragment)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 16}, {time: 18},
{ {
name: 'Keyed', name: 'Keyed',
env: 'Server', env: 'Server',
@ -2967,7 +2967,7 @@ describe('ReactFlight', () => {
children: {}, children: {},
}, },
}, },
{time: 17}, {time: 19},
] ]
: undefined, : undefined,
); );
@ -2975,7 +2975,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(thirdPartyFragment.props.children)).toEqual( expect(getDebugInfo(thirdPartyFragment.props.children)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 11}, {time: 12},
{ {
name: 'ThirdPartyAsyncIterableComponent', name: 'ThirdPartyAsyncIterableComponent',
env: 'third-party', env: 'third-party',
@ -2984,7 +2984,7 @@ describe('ReactFlight', () => {
stack: ' in Object.<anonymous> (at **)', stack: ' in Object.<anonymous> (at **)',
props: {}, props: {},
}, },
{time: 12}, {time: 13},
] ]
: undefined, : undefined,
); );
@ -3132,7 +3132,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(greeting)).toEqual( expect(getDebugInfo(greeting)).toEqual(
__DEV__ __DEV__
? [ ? [
{time: 11}, {time: 12},
{ {
name: 'Component', name: 'Component',
env: 'A', env: 'A',
@ -3144,7 +3144,7 @@ describe('ReactFlight', () => {
{ {
env: 'B', env: 'B',
}, },
{time: 12}, {time: 13},
] ]
: undefined, : undefined,
); );
@ -3332,9 +3332,9 @@ describe('ReactFlight', () => {
}, },
}; };
expect(getDebugInfo(greeting)).toEqual([ expect(getDebugInfo(greeting)).toEqual([
{time: 11},
greetInfo,
{time: 12}, {time: 12},
greetInfo,
{time: 13},
{ {
name: 'Container', name: 'Container',
env: 'Server', env: 'Server',
@ -3350,7 +3350,7 @@ describe('ReactFlight', () => {
}), }),
}, },
}, },
{time: 13}, {time: 14},
]); ]);
// The owner that created the span was the outer server component. // The owner that created the span was the outer server component.
// We expect the debug info to be referentially equal to the owner. // We expect the debug info to be referentially equal to the owner.

View File

@ -49,6 +49,7 @@ import {
enableViewTransition, enableViewTransition,
enableSwipeTransition, enableSwipeTransition,
} from 'shared/ReactFeatureFlags'; } from 'shared/ReactFeatureFlags';
import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
import ReactSharedInternals from 'shared/ReactSharedInternals'; import ReactSharedInternals from 'shared/ReactSharedInternals';
import is from 'shared/objectIs'; import is from 'shared/objectIs';
@ -1984,6 +1985,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
finishQueueingConcurrentUpdates(); finishQueueingConcurrentUpdates();
if (__DEV__) { if (__DEV__) {
resetOwnerStackLimit();
ReactStrictModeWarnings.discardPendingWarnings(); ReactStrictModeWarnings.discardPendingWarnings();
} }

View File

@ -11,15 +11,43 @@
let React; let React;
let ReactNoop; let ReactNoop;
let ReactNoopServer;
let Scheduler;
let act; let act;
let advanceTimersByTime;
let assertLog;
let serverAct;
let waitFor;
describe('ReactOwnerStacks', () => { describe('ReactOwnerStacks', () => {
beforeEach(function () { beforeEach(function () {
jest.resetModules(); let time = 10;
advanceTimersByTime = timeMS => {
jest.advanceTimersByTime(timeMS);
time += timeMS;
};
const now = jest.fn().mockImplementation(() => {
return time++;
});
Object.defineProperty(performance, 'timeOrigin', {
value: time,
configurable: true,
});
Object.defineProperty(performance, 'now', {
value: now,
configurable: true,
});
jest.resetModules();
React = require('react'); React = require('react');
ReactNoop = require('react-noop-renderer'); ReactNoop = require('react-noop-renderer');
ReactNoopServer = require('react-noop-renderer/server');
Scheduler = require('scheduler');
act = require('internal-test-utils').act; act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
serverAct = require('internal-test-utils').serverAct;
waitFor = require('internal-test-utils').waitFor;
}); });
function normalizeCodeLocInfo(str) { function normalizeCodeLocInfo(str) {
@ -85,4 +113,311 @@ describe('ReactOwnerStacks', () => {
expect(React.captureOwnerStack()).toBe(null); expect(React.captureOwnerStack()).toBe(null);
} }
}); });
// @gate __DEV__
it('cuts off at the owner stack limit', async () => {
function App({siblingsBeforeStackOne}) {
const children = [];
for (
let i = 0;
i <
siblingsBeforeStackOne -
// <App /> callsite
1 -
// Stop so that OwnerStackOne will be right before cutoff
1;
i++
) {
children.push(<Component key={i} />);
}
children.push(<OwnerStackOne key="stackOne" />);
children.push(<OwnerStackTwo key="stackTwo" />);
return children;
}
function Component() {
return null;
}
let stackOne;
function OwnerStackOne() {
stackOne = React.captureOwnerStack();
}
let stackTwo;
function OwnerStackTwo() {
stackTwo = React.captureOwnerStack();
}
await act(() => {
ReactNoop.render(
<App
key="one"
// Should be the value with of `ownerStackLimit` with `__VARIANT__` so that we see the cutoff
siblingsBeforeStackOne={500}
/>,
);
});
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: __VARIANT__
? // captured right after cutoff
'\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in App (at **)',
});
await act(() => {
ReactNoop.render(
<App
// TODO: Owner Stacks should update on re-render.
key="two"
siblingsBeforeStackOne={0}
/>,
);
});
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: __VARIANT__
? // We re-rendered immediately so not enough time has ellapsed to reset the limit.
'\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in App (at **)',
stackTwo: __VARIANT__
? // We re-rendered immediately so not enough time has ellapsed to reset the limit.
'\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in App (at **)',
});
// advance time so that we reset the limit
advanceTimersByTime(1001);
await act(() => {
ReactNoop.render(
<App
// TODO: Owner Stacks should update on re-render.
key="three"
// We reset after <App /> so we need to render one more
// to have similar cutoff as the initial render (key="one")
siblingsBeforeStackOne={501}
/>,
);
});
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: __VARIANT__
? // captured right after cutoff
'\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in App (at **)',
});
});
// @gate __DEV__
it('Fiber: resets the owner stack limit periodically', async () => {
function App({siblingsBeforeStackOne, timeout}) {
const children = [];
for (
let i = 0;
i <
siblingsBeforeStackOne -
// <App /> callsite
1 -
// Stop so that OwnerStackOne will be right before cutoff
1;
i++
) {
children.push(<Component key={i} />);
}
children.push(<OwnerStackOne key="stackOne" />);
children.push(<OwnerStackDelayed key="stackTwo" timeout={timeout} />);
return children;
}
function Component() {
return null;
}
let stackOne;
function OwnerStackOne() {
Scheduler.log('render OwnerStackOne');
stackOne = React.captureOwnerStack();
}
let stackTwo;
function OwnerStackTwo() {
Scheduler.log('render OwnerStackTwo');
stackTwo = React.captureOwnerStack();
}
function OwnerStackDelayed({timeout}) {
Scheduler.log('render OwnerStackDelayed');
React.use(timeout);
return <OwnerStackTwo />;
}
React.startTransition(() => {
ReactNoop.render(
<App
key="one"
// Should be the value with of `ownerStackLimit` with `__VARIANT__` so that we see the cutoff
siblingsBeforeStackOne={500}
timeout={
new Promise(resolve =>
setTimeout(
resolve,
// Must be greater or equal then the reset interval
1000,
),
)
}
/>,
);
});
await waitFor(['render OwnerStackOne', 'render OwnerStackDelayed']);
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
// 1 for the timeout
pendingTimers: 1,
stackOne: '\n in App (at **)',
stackTwo: undefined,
});
// resolve `timeout` Promise
advanceTimersByTime(1000);
await waitFor(['render OwnerStackDelayed', 'render OwnerStackTwo']);
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: __VARIANT__
? // We don't reset in Fiber until we start a new render.
// Here we just continued after a ping.
'\n in UnknownOwner (at **)' + '\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in OwnerStackDelayed (at **)' + '\n in App (at **)',
});
});
// @gate __DEV__
it('Fizz: resets the owner stack limit periodically', async () => {
function App({siblingsBeforeStackOne, timeout}) {
const children = [];
for (
let i = 0;
i <
siblingsBeforeStackOne -
// <App /> callsite
1 -
// Stop so that OwnerStackOne will be right before cutoff
1;
i++
) {
children.push(<Component key={i} />);
}
children.push(<OwnerStackOne key="stackOne" />);
children.push(<OwnerStackDelayed key="stackTwo" timeout={timeout} />);
return children;
}
function Component() {
return null;
}
let stackOne;
function OwnerStackOne() {
Scheduler.log('render OwnerStackOne');
stackOne = React.captureOwnerStack();
}
let stackTwo;
function OwnerStackTwo() {
Scheduler.log('render OwnerStackTwo');
stackTwo = React.captureOwnerStack();
}
function OwnerStackDelayed({timeout}) {
Scheduler.log('render OwnerStackDelayed');
React.use(timeout);
return <OwnerStackTwo />;
}
ReactNoopServer.render(
<App
key="one"
// Should be the value with of `ownerStackLimit` with `__VARIANT__` so that we see the cutoff
siblingsBeforeStackOne={500}
timeout={
new Promise(resolve =>
setTimeout(
resolve,
// Must be greater or equal then the reset interval
1000,
),
)
}
/>,
);
assertLog(['render OwnerStackOne', 'render OwnerStackDelayed']);
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
// 1 for the timeout
pendingTimers: 1,
stackOne: '\n in App (at **)',
stackTwo: undefined,
});
await serverAct(() => {
advanceTimersByTime(1000);
});
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: __VARIANT__
? // We don't reset in Fiber until we start a new render.
// Here we just continued after a ping.
'\n in UnknownOwner (at **)' + '\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in OwnerStackDelayed (at **)' + '\n in App (at **)',
});
});
}); });

View File

@ -1050,15 +1050,15 @@ describe('ReactFlightDOMEdge', () => {
owner: null, owner: null,
}); });
expect(lazyWrapper._debugInfo).toEqual([ expect(lazyWrapper._debugInfo).toEqual([
{time: 11},
greetInfo,
{time: 12}, {time: 12},
greetInfo,
{time: 13},
expect.objectContaining({ expect.objectContaining({
name: 'Container', name: 'Container',
env: 'Server', env: 'Server',
owner: greetInfo, owner: greetInfo,
}), }),
{time: 13}, {time: 14},
]); ]);
// The owner that created the span was the outer server component. // The owner that created the span was the outer server component.
// We expect the debug info to be referentially equal to the owner. // We expect the debug info to be referentially equal to the owner.

View File

@ -133,6 +133,7 @@ import {
callRenderInDEV, callRenderInDEV,
} from './ReactFizzCallUserSpace'; } from './ReactFizzCallUserSpace';
import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
import { import {
getIteratorFn, getIteratorFn,
ASYNC_ITERATOR, ASYNC_ITERATOR,
@ -473,6 +474,10 @@ export function createRequest(
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void), onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
formState: void | null | ReactFormState<any, any>, formState: void | null | ReactFormState<any, any>,
): Request { ): Request {
if (__DEV__) {
resetOwnerStackLimit();
}
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
const request: Request = new RequestInstance( const request: Request = new RequestInstance(
resumableState, resumableState,
@ -571,6 +576,10 @@ export function resumeRequest(
onFatalError: void | ((error: mixed) => void), onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void), onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
): Request { ): Request {
if (__DEV__) {
resetOwnerStackLimit();
}
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
const request: Request = new RequestInstance( const request: Request = new RequestInstance(
postponedState.resumableState, postponedState.resumableState,
@ -4616,6 +4625,7 @@ export function performWork(request: Request): void {
fatalError(request, error, errorInfo, null); fatalError(request, error, errorInfo, null);
} finally { } finally {
setCurrentResumableState(prevResumableState); setCurrentResumableState(prevResumableState);
ReactSharedInternals.H = prevDispatcher; ReactSharedInternals.H = prevDispatcher;
ReactSharedInternals.A = prevAsyncDispatcher; ReactSharedInternals.A = prevAsyncDispatcher;

View File

@ -103,6 +103,7 @@ import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner'; import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';
import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack'; import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
import { import {
callComponentInDEV, callComponentInDEV,
@ -552,6 +553,10 @@ export function createRequest(
environmentName: void | string | (() => string), // DEV-only environmentName: void | string | (() => string), // DEV-only
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
): Request { ): Request {
if (__DEV__) {
resetOwnerStackLimit();
}
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
return new RequestInstance( return new RequestInstance(
RENDER, RENDER,
@ -580,6 +585,10 @@ export function createPrerenderRequest(
environmentName: void | string | (() => string), // DEV-only environmentName: void | string | (() => string), // DEV-only
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
): Request { ): Request {
if (__DEV__) {
resetOwnerStackLimit();
}
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors // $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
return new RequestInstance( return new RequestInstance(
PRERENDER, PRERENDER,

View File

@ -0,0 +1,178 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
if (typeof Blob === 'undefined') {
global.Blob = require('buffer').Blob;
}
if (typeof File === 'undefined' || typeof FormData === 'undefined') {
global.File = require('undici').File;
global.FormData = require('undici').FormData;
}
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
const dot = name.lastIndexOf('.');
if (dot !== -1) {
name = name.slice(dot + 1);
}
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
})
);
}
let ReactServer;
let ReactNoopFlightServer;
let Scheduler;
let advanceTimersByTime;
let assertLog;
describe('ReactFlight', () => {
beforeEach(() => {
// Mock performance.now for timing tests
let time = 0;
advanceTimersByTime = timeMS => {
time += timeMS;
jest.advanceTimersByTime(timeMS);
};
const now = jest.fn().mockImplementation(() => {
return time++;
});
Object.defineProperty(performance, 'timeOrigin', {
value: time,
configurable: true,
});
Object.defineProperty(performance, 'now', {
value: now,
configurable: true,
});
jest.resetModules();
jest.mock('react', () => require('react/react.react-server'));
ReactServer = require('react');
ReactNoopFlightServer = require('react-noop-renderer/flight-server');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
});
afterEach(() => {
jest.restoreAllMocks();
});
// @gate __DEV__
it('resets the owner stack limit periodically', async () => {
function App({siblingsBeforeStackOne, timeout}) {
const children = [];
for (
let i = 0;
i <
siblingsBeforeStackOne -
// <App /> callsite
1 -
// Stop so that OwnerStackOne will be right before cutoff
1;
i++
) {
children.push(ReactServer.createElement(Component, {key: i}));
}
children.push(
ReactServer.createElement(OwnerStackOne, {key: 'stackOne'}),
);
children.push(
ReactServer.createElement(OwnerStackDelayed, {
key: 'stackTwo',
timeout,
}),
);
return children;
}
function Component() {
return null;
}
let stackOne;
function OwnerStackOne() {
Scheduler.log('render OwnerStackOne');
stackOne = ReactServer.captureOwnerStack();
}
let stackTwo;
function OwnerStackTwo() {
Scheduler.log('render OwnerStackTwo');
stackTwo = ReactServer.captureOwnerStack();
}
function OwnerStackDelayed({timeout}) {
Scheduler.log('render OwnerStackDelayed');
// Owner Stacks start fresh after `await`.
// We need to sync delay to observe the reset limit behavior.
// TODO: Is that the right behavior? If you do stack + Ownst Stack you'd get `OwnerStackTwo` twice.
jest.advanceTimersByTime(timeout);
return ReactServer.createElement(OwnerStackTwo, {});
}
ReactNoopFlightServer.render(
ReactServer.createElement(App, {
key: 'one',
// Should be the value with of `ownerStackLimit` with `__VARIANT__` so that we see the cutoff
siblingsBeforeStackOne: 500,
// Must be greater or equal then the reset interval
timeout: 1000,
}),
);
assertLog([
'render OwnerStackOne',
'render OwnerStackDelayed',
'render OwnerStackTwo',
]);
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: __VARIANT__
? // Didn't advance timers yet to reset
'\n in UnknownOwner (at **)' + '\n in UnknownOwner (at **)'
: // We never hit the limit outside __VARIANT__
'\n in OwnerStackDelayed (at **)' + '\n in App (at **)',
});
// Ensure we reset the limit after the timeout
advanceTimersByTime(1000);
ReactNoopFlightServer.render(
ReactServer.createElement(App, {
key: 'two',
siblingsBeforeStackOne: 0,
timeout: 0,
}),
);
expect({
pendingTimers: jest.getTimerCount(),
stackOne: normalizeCodeLocInfo(stackOne),
stackTwo: normalizeCodeLocInfo(stackTwo),
}).toEqual({
pendingTimers: 0,
stackOne: '\n in App (at **)',
stackTwo: '\n in OwnerStackDelayed (at **)' + '\n in App (at **)',
});
});
});

View File

@ -38,6 +38,9 @@ export type SharedStateClient = {
// ReactDebugCurrentFrame // ReactDebugCurrentFrame
getCurrentStack: null | (() => string), getCurrentStack: null | (() => string),
// ReactOwnerStackReset
recentlyCreatedOwnerStacks: 0,
}; };
export type RendererTask = boolean => RendererTask | null; export type RendererTask = boolean => RendererTask | null;
@ -58,6 +61,7 @@ if (__DEV__) {
ReactSharedInternals.thrownErrors = []; ReactSharedInternals.thrownErrors = [];
// Stack implementation injected by the current renderer. // Stack implementation injected by the current renderer.
ReactSharedInternals.getCurrentStack = (null: null | (() => string)); ReactSharedInternals.getCurrentStack = (null: null | (() => string));
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
} }
export default ReactSharedInternals; export default ReactSharedInternals;

View File

@ -39,6 +39,9 @@ export type SharedStateServer = {
// ReactDebugCurrentFrame // ReactDebugCurrentFrame
getCurrentStack: null | (() => string), getCurrentStack: null | (() => string),
// ReactOwnerStackReset
recentlyCreatedOwnerStacks: 0,
}; };
export type RendererTask = boolean => RendererTask | null; export type RendererTask = boolean => RendererTask | null;
@ -59,6 +62,7 @@ if (enableTaint) {
if (__DEV__) { if (__DEV__) {
// Stack implementation injected by the current renderer. // Stack implementation injected by the current renderer.
ReactSharedInternals.getCurrentStack = (null: null | (() => string)); ReactSharedInternals.getCurrentStack = (null: null | (() => string));
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
} }
export default ReactSharedInternals; export default ReactSharedInternals;

View File

@ -16,7 +16,10 @@ import {
} from 'shared/ReactSymbols'; } from 'shared/ReactSymbols';
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
import isArray from 'shared/isArray'; import isArray from 'shared/isArray';
import {disableDefaultPropsExceptForClasses} from 'shared/ReactFeatureFlags'; import {
disableDefaultPropsExceptForClasses,
ownerStackLimit,
} from 'shared/ReactFeatureFlags';
const createTask = const createTask =
// eslint-disable-next-line react-internal/no-production-logging // eslint-disable-next-line react-internal/no-production-logging
@ -57,12 +60,32 @@ function getOwner() {
return null; return null;
} }
/** @noinline */
function UnknownOwner() {
/** @noinline */
return (() => Error('react-stack-top-frame'))();
}
const createFakeCallStack = {
'react-stack-bottom-frame': function (callStackForError) {
return callStackForError();
},
};
let specialPropKeyWarningShown; let specialPropKeyWarningShown;
let didWarnAboutElementRef; let didWarnAboutElementRef;
let didWarnAboutOldJSXRuntime; let didWarnAboutOldJSXRuntime;
let unknownOwnerDebugStack;
let unknownOwnerDebugTask;
if (__DEV__) { if (__DEV__) {
didWarnAboutElementRef = {}; didWarnAboutElementRef = {};
// We use this technique to trick minifiers to preserve the function name.
unknownOwnerDebugStack = createFakeCallStack['react-stack-bottom-frame'].bind(
createFakeCallStack,
UnknownOwner,
)();
unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
} }
function hasValidRef(config) { function hasValidRef(config) {
@ -373,6 +396,9 @@ export function jsxProdSignatureRunningInDevWithDynamicChildren(
) { ) {
if (__DEV__) { if (__DEV__) {
const isStaticChildren = false; const isStaticChildren = false;
const trackActualOwner =
__DEV__ &&
ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
return jsxDEVImpl( return jsxDEVImpl(
type, type,
config, config,
@ -380,8 +406,14 @@ export function jsxProdSignatureRunningInDevWithDynamicChildren(
isStaticChildren, isStaticChildren,
source, source,
self, self,
__DEV__ && Error('react-stack-top-frame'), __DEV__ &&
__DEV__ && createTask(getTaskName(type)), (trackActualOwner
? Error('react-stack-top-frame')
: unknownOwnerDebugStack),
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
); );
} }
} }
@ -395,6 +427,9 @@ export function jsxProdSignatureRunningInDevWithStaticChildren(
) { ) {
if (__DEV__) { if (__DEV__) {
const isStaticChildren = true; const isStaticChildren = true;
const trackActualOwner =
__DEV__ &&
ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
return jsxDEVImpl( return jsxDEVImpl(
type, type,
config, config,
@ -402,8 +437,14 @@ export function jsxProdSignatureRunningInDevWithStaticChildren(
isStaticChildren, isStaticChildren,
source, source,
self, self,
__DEV__ && Error('react-stack-top-frame'), __DEV__ &&
__DEV__ && createTask(getTaskName(type)), (trackActualOwner
? Error('react-stack-top-frame')
: unknownOwnerDebugStack),
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
); );
} }
} }
@ -417,6 +458,9 @@ const didWarnAboutKeySpread = {};
* @param {string} key * @param {string} key
*/ */
export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) { export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
const trackActualOwner =
__DEV__ &&
ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
return jsxDEVImpl( return jsxDEVImpl(
type, type,
config, config,
@ -424,8 +468,14 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
isStaticChildren, isStaticChildren,
source, source,
self, self,
__DEV__ && Error('react-stack-top-frame'), __DEV__ &&
__DEV__ && createTask(getTaskName(type)), (trackActualOwner
? Error('react-stack-top-frame')
: unknownOwnerDebugStack),
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
); );
} }
@ -692,7 +742,9 @@ export function createElement(type, config, children) {
defineKeyPropWarningGetter(props, displayName); defineKeyPropWarningGetter(props, displayName);
} }
} }
const trackActualOwner =
__DEV__ &&
ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
return ReactElement( return ReactElement(
type, type,
key, key,
@ -700,8 +752,14 @@ export function createElement(type, config, children) {
undefined, undefined,
getOwner(), getOwner(),
props, props,
__DEV__ && Error('react-stack-top-frame'), __DEV__ &&
__DEV__ && createTask(getTaskName(type)), (trackActualOwner
? Error('react-stack-top-frame')
: unknownOwnerDebugStack),
__DEV__ &&
(trackActualOwner
? createTask(getTaskName(type))
: unknownOwnerDebugTask),
); );
} }

View File

@ -268,3 +268,5 @@ export const enableUpdaterTracking = __PROFILE__;
// Internal only. // Internal only.
export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
export const ownerStackLimit = 1e4;

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) Meta Platforms, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ReactSharedInternals from 'shared/ReactSharedInternals';
let lastResetTime = 0;
let getCurrentTime: () => number | DOMHighResTimeStamp;
const hasPerformanceNow =
// $FlowFixMe[method-unbinding]
typeof performance === 'object' && typeof performance.now === 'function';
if (hasPerformanceNow) {
const localPerformance = performance;
getCurrentTime = () => localPerformance.now();
} else {
const localDate = Date;
getCurrentTime = () => localDate.now();
}
export function resetOwnerStackLimit() {
if (__DEV__) {
const now = getCurrentTime();
const timeSinceLastReset = now - lastResetTime;
if (timeSinceLastReset > 1000) {
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
lastResetTime = now;
}
} else {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'resetOwnerStackLimit should never be called in production mode. This is a bug in React.',
);
}
}

View File

@ -29,3 +29,7 @@ export const enableUseEffectCRUDOverload = __VARIANT__;
export const enableFastAddPropertiesInDiffing = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__;
export const enableLazyPublicInstanceInFabric = __VARIANT__; export const enableLazyPublicInstanceInFabric = __VARIANT__;
export const renameElementSymbol = __VARIANT__; export const renameElementSymbol = __VARIANT__;
export const ownerStackLimit: number = __VARIANT__
? // Some value that doesn't impact existing tests
500
: 1e4;

View File

@ -31,6 +31,7 @@ export const {
enableFastAddPropertiesInDiffing, enableFastAddPropertiesInDiffing,
enableLazyPublicInstanceInFabric, enableLazyPublicInstanceInFabric,
renameElementSymbol, renameElementSymbol,
ownerStackLimit,
} = dynamicFlags; } = dynamicFlags;
// The rest of the flags are static for better dead code elimination. // The rest of the flags are static for better dead code elimination.

View File

@ -75,6 +75,7 @@ export const enableSwipeTransition = false;
export const enableFastAddPropertiesInDiffing = false; export const enableFastAddPropertiesInDiffing = false;
export const enableLazyPublicInstanceInFabric = false; export const enableLazyPublicInstanceInFabric = false;
export const enableScrollEndPolyfill = true; export const enableScrollEndPolyfill = true;
export const ownerStackLimit = 1e4;
export const enableFragmentRefs = false; export const enableFragmentRefs = false;

View File

@ -75,6 +75,7 @@ export const enableSwipeTransition = false;
export const enableFastAddPropertiesInDiffing = true; export const enableFastAddPropertiesInDiffing = true;
export const enableLazyPublicInstanceInFabric = false; export const enableLazyPublicInstanceInFabric = false;
export const enableScrollEndPolyfill = true; export const enableScrollEndPolyfill = true;
export const ownerStackLimit = 1e4;
export const enableFragmentRefs = false; export const enableFragmentRefs = false;

View File

@ -72,6 +72,7 @@ export const enableFastAddPropertiesInDiffing = false;
export const enableLazyPublicInstanceInFabric = false; export const enableLazyPublicInstanceInFabric = false;
export const enableScrollEndPolyfill = true; export const enableScrollEndPolyfill = true;
export const enableFragmentRefs = false; export const enableFragmentRefs = false;
export const ownerStackLimit = 1e4;
// Flow magic to verify the exports of this file match the original version. // Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType); ((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -88,6 +88,7 @@ export const enableLazyPublicInstanceInFabric = false;
export const enableScrollEndPolyfill = true; export const enableScrollEndPolyfill = true;
export const enableFragmentRefs = false; export const enableFragmentRefs = false;
export const ownerStackLimit = 1e4;
// Flow magic to verify the exports of this file match the original version. // Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType); ((((null: any): ExportsType): FeatureFlagsType): ExportsType);

View File

@ -43,6 +43,11 @@ export const enableComponentPerformanceTrack = __VARIANT__;
export const enableScrollEndPolyfill = __VARIANT__; export const enableScrollEndPolyfill = __VARIANT__;
export const enableFragmentRefs = __VARIANT__; export const enableFragmentRefs = __VARIANT__;
export const ownerStackLimit: number = __VARIANT__
? // Some value that doesn't impact existing tests
500
: 1e4;
// TODO: These flags are hard-coded to the default values used in open source. // TODO: These flags are hard-coded to the default values used in open source.
// Update the tests so that they pass in either mode, then set these // Update the tests so that they pass in either mode, then set these
// to __VARIANT__. // to __VARIANT__.

View File

@ -40,6 +40,7 @@ export const {
enableComponentPerformanceTrack, enableComponentPerformanceTrack,
enableScrollEndPolyfill, enableScrollEndPolyfill,
enableFragmentRefs, enableFragmentRefs,
ownerStackLimit,
} = dynamicFeatureFlags; } = dynamicFeatureFlags;
// On WWW, __EXPERIMENTAL__ is used for a new modern build. // On WWW, __EXPERIMENTAL__ is used for a new modern build.