RDEV-10097 - Dispose the plugins of a destroyed view - #218
Open
helderjgoncalves wants to merge 1 commit into
Open
RDEV-10097 - Dispose the plugins of a destroyed view#218helderjgoncalves wants to merge 1 commit into
helderjgoncalves wants to merge 1 commit into
Conversation
Plugins are not part of the react tree, so unmounting an inner view never reached them. One that registered itself in document level state - a listener on the document, an entry in a handlers queue - stayed registered, holding its view's root and the whole shadow dom under it alive. A heap snapshot of a screen that swaps inner views shows exactly that: detached shadow roots and their fibers, kept by the text selection handlers queue. Destroying a view now releases it: its plugins are disposed, its modules are dropped, and it is marked as released so that a component or a plugin that was still loading does not attach itself to it. Guarded by EnsureViewPluginsAreDisposed on the factory. Co-authored-by: Cursor <cursoragent@cursor.com>
helderjgoncalves
requested review from
OS-sandeeppal,
alvesmiguel1 and
jonpinev
August 11, 2026 18:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Inner views leak their whole shadow dom when they are destroyed.
Plugins are not part of the react tree, so unmounting a view never reaches them. A plugin that registered itself in document level state stays registered, and its registration closes over the view's root, so the shadow root and everything under it is kept alive by a module level singleton in the host document.
ViewFrameworkis the clearest case: its constructor adds handlers to the shared keyboard, mouse and text selection handlers, and those queues live for as long as the document does.A heap snapshot from a screen that swaps inner views shows exactly that shape: detached shadow roots and their react fibers, retained by
TextSelectionChangeEventHandler.endHandlersQueue.The C# side does call
Disposeon some plugins when the adapter goes, but nothing does it when the view itself is destroyed and the host keeps running, which is what happens on every inner view swap.What changed
onChildViewRemovednow releases the view before notifying the host:modulesand lets go ofroot,headand the render handlerTwo load paths were guarded with that flag, since both used to finish against a dead view:
loadPluginsno longer builds a plugin after its view is gone, andloadComponentno longer renders into it.loadScriptPerViewreturns instead of waiting on a load that a detached head can never complete.Feature toggle
ReactViewFactory.EnsureViewPluginsAreDisposed, defaulting to true, gates the release. It is a separate flag fromLoadScriptsOncePerDocumenton purpose: reusing that one would mean a host that hits a problem here has to give up the script deduplication too, and the point of a kill switch is to be able to tell which change caused what.Tests
InnerViewPluginsTests.PluginIsDisposedWhenInnerViewIsDestroyedloads an inner view with a plugin, unmounts the frame and asserts the plugin was disposed. The count also pins that only the inner view's plugin goes, not the main view's.The test suite is not discoverable on my machine (
dotnet testreports no tests for any fixture, including the existing ones), so this one has only been verified to compile — worth a look at the CI run.Test plan
Made with Cursor