DOM: shared mutation observer is never disconnected once two consumers share it
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
### Bug description
`sharedMutationObserver.observe` (`src/vs/base/browser/dom.ts`) shares one `MutationObserver` per target and options hash and counts `users`. The creating consumer registers a cleanup disposable that decrements the count and disconnects the observer when it reaches zero. A second consumer that joins an existing observer only increments `users` but registers no cleanup of its own, so once two consumers share an observer the count can never reach zero again: the first consumer's decrement takes it from 2 to 1, where it stays forever.
Result: the shared `MutationObserver` is never disconnected, its `onDidMutate` emitter is never disposed, and both the target node and the map entries stay pinned for the rest of the session.
Reachable in practice through style sharing across windows: cloning a global stylesheet makes a second store observe the same node.
### Steps to reproduce
1. Consumer A calls `sharedMutationObserver.observe(node, options)` and disposes the returned subscription after consumer B has joined.
2. Observe that `users` drops to 1 but never 0: `observer.disconnect()` never runs even after B also disposes.
### Expected behavior
Every observing consumer holds its own cleanup disposable; when the last one disposes, count reaches 0, the emitter is disposed and the observer disconnected exactly once regardless of dispose order.
### Version tested
Commit `38ec3d57f91b` on `main`; reproduced with deterministic DOM unit tests (joiner-first and creator-first dispose order). A fix is ready.
Contributor guide
Assessment
This issue has not been assessed yet.