Repository: facebook/react
Pull request: Only capture stacks for up to 10 frames for Owner Stacks

Turns out `Error.stackTraceLimit` does affect `Error()` performance (in v8). But only if you're not inspecting with a debugger (e.g. ChromeDevTools is closed). Where this matters more is for SSR where you commonly aren't actively inspecting. Safari (JSC) and Firefox (SpiderMonkey) seem to have no issue with a high stack trace limit. JSC even defaults to 100 while Firefox doesn't even have `Error.stackTraceLimit`.

A higher limit is nice though for showing sync stacks. Especially with ignore-listing, a high limit can still produce nice stacks when most of the frames end up in 3rd party code. For Owner Stacks, we usually don't need very deep stacks. You mostly just have a `.map` or `useMemo` that  creates the JSX. 

So now we're setting the limit to 10 (v8 default) which allows frameworks to use a higher limit for nice sync stacks without severely degrading performance due to React Owner Stacks. 5 didn't show a meaningful difference in apps exhausting the Owner Stack limit of 10k in SSR.

The ownerstack fixture won't show the improvement since that already uses the default stack trace limit.

One thing to keep an eye out is Hermes where I haven't tested it. Only v8, JSC and SpiderMonkey.

Changed files:
filename: packages/react/src/jsx/ReactJSXElement.js
patch: @@ -57,6 +57,14 @@ function getOwner() {
   return null;
 }
 
+// v8 (Chromium, Node.js) defaults to 10
+// SpiderMonkey (Firefox) does not support Error.stackTraceLimit
+// JSC (Safari) defaults to 100
+// The lower the limit, the more likely we'll not reach react_stack_bottom_frame
+// The higher the limit, the slower Error() is when not inspecting with a debugger.
+// When inspecting with a debugger, Error.stackTraceLimit has no impact on Error() performance (in v8).
+const ownerStackTraceLimit = 10;
+
 /** @noinline */
 function UnknownOwner() {
   /** @noinline */
@@ -352,15 +360,24 @@ export function jsxProdSignatureRunningInDevWithDynamicChildren(
     const trackActualOwner =
       __DEV__ &&
       ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
+    let debugStackDEV = false;
+    if (__DEV__) {
+      if (trackActualOwner) {
+        const previousStackTraceLimit = Error.stackTraceLimit;
+        Error.stackTraceLimit = ownerStackTraceLimit;
+        debugStackDEV = Error('react-stack-top-frame');
+        Error.stackTraceLimit = previousStackTraceLimit;
+      } else {
+        debugStackDEV = unknownOwnerDebugStack;
+      }
+    }
+
     return jsxDEVImpl(
       type,
       config,
       maybeKey,
       isStaticChildren,
-      __DEV__ &&
-        (trackActualOwner
-          ? Error('react-stack-top-frame')
-          : unknownOwnerDebugStack),
+      debugStackDEV,
       __DEV__ &&
         (trackActualOwner
           ? createTask(getTaskName(type))
@@ -379,15 +396,23 @@ export function jsxProdSignatureRunningInDevWithStaticChildren(
     const trackActualOwner =
       __DEV__ &&
       ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
+    let debugStackDEV = false;
+    if (__DEV__) {
+      if (trackActualOwner) {
+        const previousStackTraceLimit = Error.stackTraceLimit;
+        Error.stackTraceLimit = ownerStackTraceLimit;
+        debugStackDEV = Error('react-stack-top-frame');
+        Error.stackTraceLimit = previousStackTraceLimit;
+      } else {
+        debugStackDEV = unknownOwnerDebugStack;
+      }
+    }
     return jsxDEVImpl(
       type,
       config,
       maybeKey,
       isStaticChildren,
-      __DEV__ &&
-        (trackActualOwner
-          ? Error('react-stack-top-frame')
-          : unknownOwnerDebugStack),
+      debugStackDEV,
       __DEV__ &&
         (trackActualOwner
           ? createTask(getTaskName(type))
@@ -408,15 +433,23 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren) {
   const trackActualOwner =
     __DEV__ &&
     ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
+  let debugStackDEV = false;
+  if (__DEV__) {
+    if (trackActualOwner) {
+      const previousStackTraceLimit = Error.stackTraceLimit;
+      Error.stackTraceLimit = ownerStackTraceLimit;
+      debugStackDEV = Error('react-stack-top-frame');
+      Error.stackTraceLimit = previousStackTraceLimit;
+    } else {
+      debugStackDEV = unknownOwnerDebugStack;
+    }
+  }
   return jsxDEVImpl(
     type,
     config,
     maybeKey,
     isStaticChildren,
-    __DEV__ &&
-      (trackActualOwner
-        ? Error('react-stack-top-frame')
-        : unknownOwnerDebugStack),
+    debugStackDEV,
     __DEV__ &&
       (trackActualOwner
         ? createTask(getTaskName(type))
@@ -667,15 +700,23 @@ export function createElement(type, config, children) {
   const trackActualOwner =
     __DEV__ &&
     ReactSharedInternals.recentlyCreatedOwnerStacks++ < ownerStackLimit;
+  let debugStackDEV = false;
+  if (__DEV__) {
+    if (trackActualOwner) {
+      const previousStackTraceLimit = Error.stackTraceLimit;
+      Error.stackTraceLimit = ownerStackTraceLimit;
+      debugStackDEV = Error('react-stack-top-frame');
+      Error.stackTraceLimit = previousStackTraceLimit;
+    } else {
+      debugStackDEV = unknownOwnerDebugStack;
+    }
+  }
   return ReactElement(
     type,
     key,
     props,
     getOwner(),
-    __DEV__ &&
-      (trackActualOwner
-        ? Error('react-stack-top-frame')
-        : unknownOwnerDebugStack),
+    debugStackDEV,
     __DEV__ &&
       (trackActualOwner
         ? createTask(getTaskName(type))