Skip to content

fix(driver): evict animateExit listeners; install DevAPI closures once#256

Merged
zzal merged 2 commits into
mainfrom
fix/animate-exit-eviction
Jul 19, 2026
Merged

fix(driver): evict animateExit listeners; install DevAPI closures once#256
zzal merged 2 commits into
mainfrom
fix/animate-exit-eviction

Conversation

@zzal

@zzal zzal commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Audit backlog B1 + B3 (the two closure-lifecycle Highs from Parts 2/6).

animateExit listener leak (B1). The opcode skipped the listener-eviction sweep its sibling destroyNode runs — and Swift suppresses destroyNode for the animated root, so every exit-animated unmount whose root carried handlers (every Toast dismissal) leaked a listeners-map entry and kept a stale DOM listener dispatching into evicted handler ids during the animation window. Both opcodes now share one detachListeners sweep; animateExit detaches immediately, matching the Swift-side handler eviction. Adds the first driver-layer animateExit tests (it was the only opcode with zero coverage): immediate detach blocks in-window dispatch, node removed after the window, parentNode fallback.

DevAPI closure pinning (B3). DevAPI.installAll() re-created its four JSClosures on every render(into:)/unmount(into:) and overwrote the fields without release() — four entries pinned in sharedClosures per cycle in every dev session. The closures read the live renderers set at call time, so a single guarded install is equivalent (same rationale/pattern as HMRBridge).

Verification. js-driver: 96 tests, 0 fail. Whole-package swift test: 1901 tests green. EmbeddedDriver.swift + 22 example runtime-JS copies regenerated via swiflow-codegen driver (embed-freshness gate covers them).

🤖 Generated with Claude Code

zzal and others added 2 commits July 19, 2026 18:12
The animateExit opcode skipped the listener-eviction sweep its sibling
destroyNode runs — and the Swift side suppresses destroyNode for the
animated root, so every exit-animated unmount whose root carried
handlers (every Toast dismissal) leaked a listeners-map entry and kept
a stale DOM listener dispatching into evicted handler ids during the
animation window. Both opcodes now share one detachListeners sweep;
animateExit detaches immediately, matching the Swift-side eviction.

DevAPI.installAll re-created its four JSClosures on every render/
unmount without releasing the prior set — each overwrite pinned four
entries in JavaScriptKit's static sharedClosures table. The closures
read the live renderers set at call time, so a single guarded install
is equivalent (the HMRBridge pattern).

js-driver: 96 tests, 0 fail (adds the first animateExit driver-layer
coverage: immediate detach, post-window removal, parentNode fallback).
Whole-package swift test: 1901 green. EmbeddedDriver + 22 example
copies regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📦 Bundle size

Artifact Baseline This PR Δ
App.wasm 5.06 MB 5.23 MB +3.46%
App.wasm (gzip) 1.80 MB 1.87 MB +3.57%
JS runtime 54.5 KB 54.5 KB +0.00%
JS runtime (gzip) 11.3 KB 11.3 KB +0.00%
Total (gzip) 1.81 MB 1.88 MB +3.55%

✅ Within budget (≤5% growth allowed).

Baseline: Swift 6.3, WASM SDK 6.3-RELEASE, measured 2026-06-18.

@zzal
zzal merged commit eb685c8 into main Jul 19, 2026
6 checks passed
@zzal
zzal deleted the fix/animate-exit-eviction branch July 19, 2026 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant