ワンクリックで
lifecycle-guard
Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | lifecycle_guard |
| description | Protocols for preventing memory leaks and event listener accumulation in VS Code webview extensions. |
Webview components in VS Code often rely on global event listeners (window.addEventListener) and reactive stores. In a test environment like vitest (jsdom), these listeners persist across test suites unless explicitly removed, causing memory leaks, process hangs, and erratic test failures.
Every singleton or component that registers global listeners MUST implement a dispose() or cleanup() method.
export class MessageClient {
private handlers = new Map<string, Function>();
constructor() {
this.onMessage = this.onMessage.bind(this);
window.addEventListener('message', this.onMessage);
}
public dispose() {
window.removeEventListener('message', this.onMessage);
this.handlers.clear();
}
}
Use a vitest.setup.ts file to mock missing browser APIs (e.g., indexedDB, scrollIntoView) and ensure environmental consistency.
import { vi } from 'vitest';
// Global mocks
if (typeof window !== 'undefined') {
window.scrollIntoView = vi.fn();
}
// Global cleanup hooks
afterEach(() => {
WebviewStore.getInstance().dispose();
MessageClient.getInstance().dispose();
});
setTimeout, setInterval, or addEventListener calls that lack a corresponding remove or clear call.[!WARNING] Scope: Test environment only. This is NOT a production concern.
WebviewAudioEngine.playBlob() sets audio.src, calls audio.load(), then awaits canplay → play() → ended. In jsdom, HTMLAudioElement.load() is a stub — it executes synchronously but dispatches no media events. This means canplay never fires and the inner Promise in playBlob() hangs indefinitely.
Symptom: Vitest reports Error: Test timed out in 5000ms for any test directly calling playBlob().
In beforeEach of any test suite that exercises playBlob(), mock load() to synchronously dispatch canplay:
// For instance-specific audio elements (preferred when you have engine.audioElement):
const audio = engine.audioElement;
vi.spyOn(audio, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
// For all HTMLAudioElement instances (use when engine is reconstructed per-test):
vi.spyOn(HTMLMediaElement.prototype, 'load').mockImplementation(function(this: HTMLAudioElement) {
this.dispatchEvent(new Event('canplay'));
});
After applying the mock:
ended listener IS registered by playBlob() (verify via addEventListener spy).ended listener to resolve the Promise.engine.isBusy() returns false after await playPromise.tests/webview/core/RaceCondition.test.ts:47 — "SHOULD allow audio packets that match the current intent"tests/webview/core/WebviewAudioEngine.test.ts:49 — "should acquire lock for playBlob and release it on completion"# Dev Cycle Protocol: CDP Shell Sovereignty (v2.5.1)
Protocol for high-density, symmetrical shorthand logging in VS Code extensions.
Protocol for local session metadata persistence in the Virgo extension.
Architectural map and development guidelines for the Virgo extension. Mandatory reference for all agent modifications.
Protocol for high-integrity conversational AI injections and sensory parity in the Virgo extension.
Governance protocol and automated scripts for packaging and publishing the Virgo MCP standalone server to the public NPM registry. Enforces safety gates and clean extraction logic.