@@ -1746,90 +1746,6 @@ function intoTarget(into: IntoTarget): Cursor {
17461746 }
17471747}
17481748
1749- /**
1750- * Set of cleanup functions for active GXT renderComponent calls, drained
1751- * by __gxtCleanupActiveComponents (called from QUnit testStart and from
1752- * test-case afterEach in internal-test-helpers). Without this, reactors
1753- * registered by _renderComponentGxt leak across tests because the
1754- * synthetic `{}` owner default is not in any destroy chain — nobody
1755- * ever calls destroy() on a freshly-allocated plain object.
1756- */
1757- const _gxtPendingRenderCleanups = new Set < ( ) => void > ( ) ;
1758- ( globalThis as any ) . __gxtDrainPendingRenderCleanups = function ( ) {
1759- if ( _gxtPendingRenderCleanups . size === 0 ) return ;
1760- for ( const fn of Array . from ( _gxtPendingRenderCleanups ) ) {
1761- try {
1762- fn ( ) ;
1763- } catch {
1764- /* ignore */
1765- }
1766- }
1767- _gxtPendingRenderCleanups . clear ( ) ;
1768- } ;
1769-
1770- /**
1771- * Wire `owner` into the destroyable chain so the registered cleanup
1772- * actually fires on teardown.
1773- *
1774- * The renderComponent API documents `owner = {}` as the default. That
1775- * synthetic plain object isn't in any destroy chain by itself — calling
1776- * registerDestructor on it records the destructor but it never runs,
1777- * because nothing ever calls destroy() on the synthetic owner.
1778- *
1779- * Three layers of cleanup, all pointing at the same cleanupFn:
1780- *
1781- * 1. Ambient parent owner via associateDestroyableChild — when the
1782- * parent owner is destroyed via the standard glimmer/destroyable
1783- * chain, the synthetic descends with it.
1784- * 2. registerDestructor on owner — covers callers that do call
1785- * destroy(owner) directly.
1786- * 3. _gxtPendingRenderCleanups Set — eagerly drained by
1787- * __gxtCleanupActiveComponents at QUnit testStart and from
1788- * test-case afterEach; this is the only layer that fires
1789- * synchronously regardless of the runloop, which is what
1790- * actually prevents the cross-test reactor leak.
1791- *
1792- * Idempotent — safe to call from both _renderComponentGxt return paths.
1793- */
1794- function _wireOwnerDestroyChain ( owner : object , parentOwner : unknown , cleanupFn : ( ) => void ) : void {
1795- // Wrap so each layer fires the underlying cleanup exactly once and
1796- // removing from the pending Set is automatic.
1797- let fired = false ;
1798- const oneShot = ( ) => {
1799- if ( fired ) return ;
1800- fired = true ;
1801- _gxtPendingRenderCleanups . delete ( oneShot ) ;
1802- try {
1803- cleanupFn ( ) ;
1804- } catch {
1805- /* ignore */
1806- }
1807- } ;
1808- _gxtPendingRenderCleanups . add ( oneShot ) ;
1809- if (
1810- parentOwner &&
1811- parentOwner !== owner &&
1812- typeof owner === 'object' &&
1813- owner !== null &&
1814- Object . getPrototypeOf ( owner ) === Object . prototype &&
1815- ! isDestroyed ( parentOwner ) &&
1816- ! isDestroying ( parentOwner ) &&
1817- ! isDestroyed ( owner ) &&
1818- ! isDestroying ( owner )
1819- ) {
1820- try {
1821- associateDestroyableChild ( parentOwner as any , owner as any ) ;
1822- } catch {
1823- // parent may not accept association; fall through to register
1824- }
1825- }
1826- try {
1827- registerDestructor ( owner , oneShot ) ;
1828- } catch {
1829- // owner may not be destroyable; nothing else to do
1830- }
1831- }
1832-
18331749/**
18341750 * GXT-specific renderComponent implementation.
18351751 * Bypasses Glimmer VM bytecode compilation entirely.
@@ -1863,18 +1779,11 @@ function _renderComponentGxt(
18631779 ensureGxtContext ( ) ;
18641780
18651781 let destroyed = false ;
1866- let reactorCleanedUp = false ;
18671782 let classicReactorUnsub : ( ( ) => void ) | null = null ;
18681783
1869- // Reactor-only cleanup. Fires when the owner is destroyed via the
1870- // standard destroyable chain (registered below). Cleaning up the
1871- // classic-tag reactor is critical to prevent cross-test leaks; the
1872- // DOM is not touched here because owner destruction does not imply
1873- // the caller wants the rendered nodes removed (that is what
1874- // result.destroy() means, see doDestroy).
1875- const cleanupReactor = ( ) => {
1876- if ( reactorCleanedUp ) return ;
1877- reactorCleanedUp = true ;
1784+ const doDestroy = ( ) => {
1785+ if ( destroyed ) return ;
1786+ destroyed = true ;
18781787 if ( classicReactorUnsub ) {
18791788 try {
18801789 classicReactorUnsub ( ) ;
@@ -1883,14 +1792,6 @@ function _renderComponentGxt(
18831792 }
18841793 classicReactorUnsub = null ;
18851794 }
1886- } ;
1887-
1888- // Full destroy. Returned to the caller as result.destroy(). Cleans
1889- // the reactor and wipes the rendered content.
1890- const doDestroy = ( ) => {
1891- if ( destroyed ) return ;
1892- destroyed = true ;
1893- cleanupReactor ( ) ;
18941795 if ( targetElement instanceof Element ) {
18951796 targetElement . innerHTML = '' ;
18961797 }
@@ -1965,7 +1866,12 @@ function _renderComponentGxt(
19651866
19661867 const result : RenderResult = { destroy : doDestroy } ;
19671868 RENDER_CACHE . set ( into , { result, glimmerResult : undefined } ) ;
1968- _wireOwnerDestroyChain ( owner , prevOwner , cleanupReactor ) ;
1869+ // Register destructor on owner so owner.destroy() cleans up DOM
1870+ try {
1871+ registerDestructor ( owner , doDestroy ) ;
1872+ } catch {
1873+ // owner may not be destroyable
1874+ }
19691875 return result ;
19701876 }
19711877
@@ -2277,7 +2183,13 @@ function _renderComponentGxt(
22772183
22782184 const result : RenderResult = { destroy : doDestroy } ;
22792185 RENDER_CACHE . set ( into , { result, glimmerResult : undefined } ) ;
2280- _wireOwnerDestroyChain ( owner , prevOwner , cleanupReactor ) ;
2186+
2187+ // Register destructor on owner so that destroying the owner cleans up the DOM
2188+ try {
2189+ registerDestructor ( owner , doDestroy ) ;
2190+ } catch {
2191+ // owner may not be destroyable
2192+ }
22812193
22822194 return result ;
22832195}
0 commit comments