[controls] fix memory leak in `Window`
Fixes: https://github.com/dotnet/maui/issues/10029
I could reproduce a memory leak by doing:
App.Current.OpenWindow(new Window { Page = new ContentPage() });
I found that App.Current._requestedWindows just held onto every Window object forever.
This is a perfect example of where using ConditionalWeakTable solves the issue:
https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.conditionalweaktable-2
The only other change I made was to use Guid.ToString("n") as it removes {, }, and - characters from the string.
After these changes, I still found Window objects hanging around that appeared to be held onto by many other objects.
Then I reviewed:
void IWindow.Destroying()
{
SendWindowDisppearing();
Destroying?.Invoke(this, EventArgs.Empty);
OnDestroying();
Application?.RemoveWindow(this);
// This wasn't here!
Handler?.DisconnectHandler();
}
It appears that upon Window's closing, we didn't have any code that "disconnected" the MAUI handler. After this change, I could see Window objects properly go away.
I added a unit test as well.
/azp run
Azure Pipelines successfully started running 2 pipeline(s).
This might cause a test failure on iOS:

Can repro the test failure locally, I can say that I am a bit confused:

There might be an issue in this test, will look into it.
Ok the test uses async void to do work:
https://github.com/dotnet/maui/blob/53f6e393750a3df05b12fcde442daf3616c216f8/src/Controls/tests/DeviceTests/Elements/Window/WindowTests.cs#L125
xunit is moving on as if the test passed, but test code is continuing to run.
Will review if we have a better way to do this.
Will come back to this after this one lands:
https://github.com/dotnet/maui/pull/13536
/backport to net7.0
Started backporting to net7.0: https://github.com/dotnet/maui/actions/runs/4315741361
@PureWeen backporting to net7.0 failed, the patch most likely resulted in conflicts:
$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: [controls] fix memory leak in `Window`
Using index info to reconstruct a base tree...
M src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs
M src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs
M src/Controls/tests/Core.UnitTests/WindowsTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Controls/tests/Core.UnitTests/WindowsTests.cs
CONFLICT (content): Merge conflict in src/Controls/tests/Core.UnitTests/WindowsTests.cs
Auto-merging src/Controls/src/Core/HandlerImpl/Window/Window.Impl.cs
Auto-merging src/Controls/src/Core/HandlerImpl/Application/Application.Impl.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 [controls] fix memory leak in `Window`
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128
Please backport manually!
@PureWeen an error occurred while backporting to net7.0, please check the run log for details!
Error: git am failed, most likely due to a merge conflict.