mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
`Transformer.Definition.dispose` is documented as "automatically called on deleting an object," but `State.dispose()` only disposed its own event subjects and action manager — it never iterated still-live cells to call their per-transformer dispose. Cells holding GL buffers, mesh data, etc. only had their dispose fired on explicit deletion (e.g. `clear()`), so any consumer that called `plugin.dispose()` without first awaiting `plugin.clear()` retained the callback chain, the GL buffers it points at, and any closures captured by it. In a long-running single-page app where the user navigates between routes that mount/unmount a Mol* viewer, this leaked roughly 25–50 MB of process RSS per cycle even with `plugin.dispose()` correctly called. A 20-cycle E2E mount/unmount harness on a 1AKE structure measured a +541 MB RSS / +266 MB JS-heap delta in the unconditional-`dispose()` case; calling `await plugin.clear()` before `plugin.dispose()` halved the residual leak, confirming the per-cell dispose path was missing on the unconditional `dispose()` route. This change walks the cell tree once (post-order via the existing `StateTree.doPostOrder` helper) and invokes the per-transformer dispose for every still-live cell, swallowing+warning on errors so a single faulty transformer can't prevent siblings from cleaning up. The existing per-cell `dispose` helper is reused for consistency with `updateNode`/`findDeletes` semantics. Tests cover: chained transformers, sibling subtrees, throwing-dispose isolation, and transformers without a dispose definition. Also adds `useDefineForClassFields: false` to the jest esbuild transform so tests can construct `State` (the `TransientTree` parameter property + class field pattern relies on legacy class-field semantics, which `tsc` honors via `target: es2018` but esbuild's default `esnext` target does not). Fixes #1825 Co-authored-by: Armando Pellegrini <tech.tools@boltz.bio>
7.1 KiB
7.1 KiB