From 638933418e39ed8b50110cbd31e890839cff99ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 3 Jun 2025 10:58:00 -0700 Subject: [PATCH] Fix crash when accessing offsetParent from documentElement (#51773) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51773 Changelog: [internal] (This isn't a public API yet so not marking it as a bugfix). This fixes a crash in the `offsetParent` DOM API when called on a node whose parent is the `documentElement`. Reviewed By: andrewdacenko Differential Revision: D75876349 fbshipit-source-id: d90fcc3cb98f592c396cb0f5ee6ec80c9570745a --- .../ReactCommon/react/renderer/dom/DOM.cpp | 14 +++++++++----- .../nodes/__tests__/ReactNativeDocument-itest.js | 4 ++++ .../nodes/__tests__/ReactNativeElement-itest.js | 4 +++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp b/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp index 811e59cdb1f..29412339e8a 100644 --- a/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp +++ b/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp @@ -72,9 +72,15 @@ ShadowNode::Shared getPositionedAncestorOfShadowNodeInRevision( auto ancestors = shadowNode.getFamily().getAncestors(*currentRevision); if (ancestors.empty()) { + // The node is no longer part of an active shadow tree, or is the root. return nullptr; } + if (ancestors.size() == 1) { + // The parent is the root + return currentRevision; + } + for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { const auto layoutableAncestorShadowNode = dynamic_cast(&(it->first.get())); @@ -86,11 +92,9 @@ ShadowNode::Shared getPositionedAncestorOfShadowNodeInRevision( // We have found our nearest positioned ancestor, now to get a shared // pointer of it it++; - if (it != ancestors.rend()) { - return it->first.get().getChildren().at(it->second); - } - // else the positioned ancestor is the root which we return outside of the - // loop + return it == ancestors.rend() + ? currentRevision + : it->first.get().getChildren().at(it->second); } } diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeDocument-itest.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeDocument-itest.js index d6af1dafbe7..11aeacfa63d 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeDocument-itest.js +++ b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeDocument-itest.js @@ -122,6 +122,10 @@ describe('ReactNativeDocument', () => { expect(y).toBe(0); expect(width).toBe(200); expect(height).toBe(100); + + expect(document.documentElement.offsetParent).toBe(null); + expect(document.documentElement.offsetTop).toBe(0); + expect(document.documentElement.offsetLeft).toBe(0); }); it('implements compareDocumentPosition correctly', () => { diff --git a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js index 1803022c8b4..dccd3d38895 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js +++ b/packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js @@ -1064,7 +1064,7 @@ describe('ReactNativeElement', () => { }); describe('offsetParent / offsetTop / offsetLeft', () => { - it('retun the rounded offset values and the parent, or null and zeros when disconnected or hidden', () => { + it('return the rounded offset values and the parent, or null and zeros when disconnected or hidden', () => { const parentRef = createRef(); const elementRef = createRef(); @@ -1089,6 +1089,8 @@ describe('ReactNativeElement', () => { expect(element.offsetLeft).toBe(5); expect(element.offsetParent).toBe(parentElement); + expect(parentElement.offsetParent).toBe(root.document.documentElement); + Fantom.runTask(() => { root.render(