Files
holesail-browser/extension/background/tab-lifecycle.js
T
Raven Scott 0b31e7faa6
CI / Build & Test (push) Successful in 2m52s
fix: resolve 35 memory leaks, resource leaks, and bugs across native host and extension
CRITICAL:
- certificate-authority.js: declare `regenerated` variable in installRootCA Windows path to prevent ReferenceError crash

HIGH:
- virtual-hosts.js/service-tunnels.js: call hs.removeAllListeners() in catch blocks to prevent stale listeners on failed Holesail instances
- https-proxy.js: destroy rawSocket in TLS error handler to prevent file descriptor exhaustion
- message-router.js (native): move setEventEmitter() to module-level init instead of re-calling on every message
- ssh-manager.js: add error handler to WS server to prevent unhandled error crashes
- init.js: store setInterval ID and clear on beforeunload to prevent interval accumulation
- logs.js: store and remove chrome.runtime.onMessage listener on beforeunload; add duplicate-call guard
- events.js: move pending++ before async sendMessage call to fix SSH/RDP-only import showing "Nothing to import"
- ssh.js: store resizeTimer on activeSshSession and clear in disconnectSsh; fix auto-reconnect race with _sshConnecting lock
- native-messaging.js: track retry timer IDs in array and cancel all on disconnect
- rdp.js: reuse single offscreen canvas per session instead of allocating per bitmap

MEDIUM:
- virtual-hosts.js/service-tunnels.js: clear existing.reconnectTimer before replacing tunnel entries
- message-router.js (native): destroy pingTunnel socket on error path; clear 15s fallback timer via finally()
- startup.js: wrap setImmediate body in try/finally to always resolve proxiesReadyPromise
- https-proxy.js: fix pre-connect upstream error handler to avoid writing raw HTTP into piped TLS stream; move HOP_BY_HOP to module-level constant
- connect-proxy.js: destroy upstreamSocket on clientSocket close; track and destroy active sockets in stop()
- ssh-manager.js: call cancelPasswordWatch on WS disconnect during password collection
- rdp-manager.js: remove dead remotePort variable; add error handlers to both WS servers
- backup-manager.js: log cleanupStaging errors and non-zero exit codes
- rdp.js: null out ws callbacks before closing in disconnectRdp; disconnect MutationObserver on beforeunload
- proxy-ca.js: prune stale entries from validationResults Map in renderValidatorTable
- refresh.js: deduplicate in-flight pings per port via Set
- messaging.js: read chrome.runtime.lastError in sendToNative callback
- servers.js (dashboard): add null check for $('serverEditId') element

LOW:
- port-allocator.js: add dedup check before pushing to tunnelPortFreeList
- servers.js (native): add error listener to server-mode Holesail instances
- virtual-hosts.js: remove dead prevReconnectDelay variable
- tab-lifecycle.js: change swarmRefCount fallback from || 1 to || 0 to prevent premature swarm destroy
- ssh.js/rdp.js: disconnect MutationObservers on beforeunload
2026-03-01 00:10:20 -05:00

33 lines
1.2 KiB
JavaScript

// Tab lifecycle: clean up swarms and subscriptions when tabs close.
// Depends on: state.js (tabSwarms, swarmRefCount), logs.js (log), native-messaging.js (send, subscribedTabs, dashboardTabs)
browser.tabs.onRemoved.addListener((tabId) => {
log('Tab closed:', tabId);
const swarmIds = tabSwarms.get(tabId);
if (swarmIds) {
for (const swarmId of swarmIds) {
const count = (swarmRefCount.get(swarmId) || 0) - 1;
swarmRefCount.set(swarmId, count);
log('Swarm', swarmId, 'refcount now:', count);
if (count <= 0) {
log('Last tab for swarm', swarmId, '- destroying');
send({ type: 'destroy', payload: { swarmId } }).catch(() => {});
swarmRefCount.delete(swarmId);
}
}
tabSwarms.delete(tabId);
}
subscribedTabs.delete(tabId);
dashboardTabs.delete(tabId);
});
// Clean up subscription Sets when a tab navigates away. Without this, a tab
// that subscribes and then navigates to a non-extension page stays in the Sets
// until it is closed, causing silent sendMessage failures on every broadcast.
browser.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.url) {
subscribedTabs.delete(tabId);
dashboardTabs.delete(tabId);
}
});