From fe6c000e7f4ea1ca516d522d4894eab264a556ee Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Fri, 9 Oct 2026 16:41:05 +0530 Subject: [PATCH 1/2] DOM: Fix getOffsetParent infinite loop on a non-element parent --- packages/dom/CHANGELOG.md | 4 ++++ packages/dom/src/dom/get-offset-parent.ts | 13 ++++++------- packages/dom/src/test/dom.jsdom.test.js | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 7e652f611a008d..8b24f87323eba2 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -10,6 +10,10 @@ - Refine the public types: `getScrollContainer` types `direction` as a union, `isEmpty` takes any `Node`, `isFormElement` accepts a missing element, `getPhrasingContentSchema` returns `ContentSchema`, `allowEmpty` is optional in a `Schema` item, and `Schema` and `SchemaItem` are exported ([#84317](https://github.com/WordPress/gutenberg/pull/84317)). +### Bug Fixes + +- `getOffsetParent`: Fix an infinite loop when the parent of the node is not an element. + ### Internal - Refactor to TypeScript ([#84317](https://github.com/WordPress/gutenberg/pull/84317)). diff --git a/packages/dom/src/dom/get-offset-parent.ts b/packages/dom/src/dom/get-offset-parent.ts index 2c73032ab2e878..3da381b04f30c2 100644 --- a/packages/dom/src/dom/get-offset-parent.ts +++ b/packages/dom/src/dom/get-offset-parent.ts @@ -1,4 +1,5 @@ import getComputedStyle from './get-computed-style'; +import isElement from './is-element'; /** * Returns the closest positioned element, or null under any of the conditions @@ -14,11 +15,9 @@ import getComputedStyle from './get-computed-style'; export default function getOffsetParent( node: Node ): Node | null { // Cannot retrieve computed style or offset parent only anything other than // an element node, so find the closest element node. - let closestElement; - while ( ( closestElement = node.parentNode as Node ) ) { - if ( closestElement.nodeType === closestElement.ELEMENT_NODE ) { - break; - } + let closestElement = node.parentNode; + while ( closestElement && ! isElement( closestElement ) ) { + closestElement = closestElement.parentNode; } if ( ! closestElement ) { @@ -27,10 +26,10 @@ export default function getOffsetParent( node: Node ): Node | null { // If the closest element is already positioned, return it, as offsetParent // does not otherwise consider the node itself. - if ( getComputedStyle( closestElement as Element ).position !== 'static' ) { + if ( getComputedStyle( closestElement ).position !== 'static' ) { return closestElement; } // offsetParent is undocumented/draft. - return ( closestElement as Node & { offsetParent: Node } ).offsetParent; + return ( closestElement as HTMLElement ).offsetParent; } diff --git a/packages/dom/src/test/dom.jsdom.test.js b/packages/dom/src/test/dom.jsdom.test.js index 162ffcfac07bbd..4620e29ddfd67c 100644 --- a/packages/dom/src/test/dom.jsdom.test.js +++ b/packages/dom/src/test/dom.jsdom.test.js @@ -5,6 +5,7 @@ import { isTextField, removeInvalidHTML, isEmpty, + getOffsetParent, } from '../dom'; import { getPhrasingContentSchema } from '../phrasing-content'; @@ -170,6 +171,26 @@ describe( 'DOM', () => { ); } ); } ); + + describe( 'getOffsetParent', () => { + it( 'should return the closest element when it is positioned', () => { + parent.style.position = 'relative'; + const text = document.createTextNode( 'text' ); + parent.appendChild( text ); + + expect( getOffsetParent( text ) ).toBe( parent ); + } ); + + it( 'should return null when no ancestor is an element', () => { + expect( getOffsetParent( document.documentElement ) ).toBe( null ); + + const fragment = document.createDocumentFragment(); + const text = document.createTextNode( 'text' ); + fragment.appendChild( text ); + + expect( getOffsetParent( text ) ).toBe( null ); + } ); + } ); } ); describe( 'removeInvalidHTML', () => { From 3c54f36af17a079e5e553937d3e8d30178ffd67d Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Fri, 9 Oct 2026 16:48:48 +0530 Subject: [PATCH 2/2] DOM: Link the PR in the changelog entry --- packages/dom/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 8b24f87323eba2..86b10c3ae9bb1b 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -12,7 +12,7 @@ ### Bug Fixes -- `getOffsetParent`: Fix an infinite loop when the parent of the node is not an element. +- `getOffsetParent`: Fix an infinite loop when the parent of the node is not an element ([#84374](https://github.com/WordPress/gutenberg/pull/84374)). ### Internal