Browse Source
TopLevel teardown (HandleClosed) was only ever triggered by the backend invoking ITopLevelImpl.Closed. Browser, iOS, Android, macOS and the offscreen (designer previewer) impls never raise it from Dispose(), so EmbeddableControlRoot.Closed was never raised there and StopRendering() was never reached - the top level stayed registered in MediaContext forever. Make teardown idempotent behind EnsureClosed() and call it from the managed Dispose() paths (EmbeddableControlRoot, OffscreenTopLevel, PopupRoot). The guard lives in a new non-virtual entry point because WindowBase and Window override HandleClosed and run side effects before calling base. Also drop ChoreographerTimer's view-visibility gate on Android. It predates the render timer rewrite and vetoed ticks that DefaultRenderLoop had explicitly asked for, so the synchronous compositor round-trip in HandleClosed (Renderer.Dispose -> MediaContext.SyncDisposeCompositionTarget) could never complete once the view had unsubscribed - deadlocking every activity destroy. DefaultRenderLoop already owns the sleep/wake state machine, driven by StartRendering/StopRendering, which makes the extra gate redundant. Verified at runtime on X11, Wayland, Win32, macOS, Headless, Browser (WASM), Android and iOS: Closed fires exactly once per teardown, MediaContext returns to baseline, and platform-initiated closes still fire exactly once. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>pull/22069/head
committed by
GitHub
7 changed files with 99 additions and 33 deletions
Loading…
Reference in new issue