diff --git a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js index e18c2d7baa1..8b725bef121 100644 --- a/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js +++ b/packages/react-devtools-shared/src/backend/utils/parseStackTrace.js @@ -59,7 +59,7 @@ function parseStackTraceFromChromeStack( return parsedFrames; } -const firefoxFrameRegExp = /^((?:.*".+")?[^@]*)@(.+):(\d+):(\d+)$/; +const firefoxFrameRegExp = /^([^@"]*(?:"[^"]*"[^@"]*)*)@(.+):(\d+):(\d+)$/; function parseStackTraceFromFirefoxStack( stack: string, skipFrames: number, @@ -94,7 +94,7 @@ function parseStackTraceFromFirefoxStack( return parsedFrames; } -const CHROME_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; +const CHROME_STACK_REGEXP = /^\s*at /m; export function parseStackTraceFromString( stack: string, skipFrames: number, diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index a03ccc161ad..42ac22588c4 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -661,7 +661,8 @@ export function finalizeInitialChildren( case 'img': return true; default: - return false; + // autoFocus is a global HTML attribute that applies to all elements. + return !!props.autoFocus; } } @@ -891,6 +892,13 @@ export function commitMount( } return; } + default: { + // autoFocus is a global HTML attribute that applies to all elements. + if (newProps.autoFocus) { + ((domElement: any): HTMLElement).focus(); + } + return; + } } } diff --git a/packages/react-dom/src/__tests__/ReactDOM-test.js b/packages/react-dom/src/__tests__/ReactDOM-test.js index 5397de0e251..fb25f8897e0 100644 --- a/packages/react-dom/src/__tests__/ReactDOM-test.js +++ b/packages/react-dom/src/__tests__/ReactDOM-test.js @@ -386,6 +386,73 @@ describe('ReactDOM', () => { } }); + it('calls focus() on autoFocus anchor elements after they have been mounted to the DOM', async () => { + const originalFocus = HTMLElement.prototype.focus; + + try { + let focusedElement; + let anchorFocusedAfterMount = false; + + HTMLElement.prototype.focus = function () { + focusedElement = this; + anchorFocusedAfterMount = !!this.parentNode; + }; + + const container = document.createElement('div'); + document.body.appendChild(container); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +
, + ); + }); + + expect(anchorFocusedAfterMount).toBe(true); + expect(focusedElement.tagName).toBe('A'); + } finally { + HTMLElement.prototype.focus = originalFocus; + } + }); + + it('calls focus() on autoFocus for any focusable element after mount', async () => { + const originalFocus = HTMLElement.prototype.focus; + + try { + let focusedElement; + let elementFocusedAfterMount = false; + + HTMLElement.prototype.focus = function () { + focusedElement = this; + elementFocusedAfterMount = !!this.parentNode; + }; + + const container = document.createElement('div'); + document.body.appendChild(container); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render( +