Prevent crashes when user spams Cmd:T (#1404)

When a user spams Cmd:T, a WaveTabView might be created but never end up
getting mounted to the window, since another will come along before it
can. In these cases, the WaveTabView is essentially in a bad state and
attempting to switch to it will result in the window becoming
unresponsive. While we could recover it by running waveInit again, it's
easier to just dispose of it and treat it as an unloaded tab next time
it gets switched to.

This adds a timeout to each WaveTabView where once it gets assigned a
tab ID, it has 1 second to respond "ready" or it will be destroyed. This
should help prevent resource leakages for these dead views.
This commit is contained in:
Evan Simkowitz 2024-12-05 23:03:30 -05:00 committed by GitHub
parent 5744f4b06f
commit 3c69237d9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -32,15 +32,18 @@ export function getWaveTabViewByWebContentsId(webContentsId: number): WaveTabVie
export class WaveTabView extends WebContentsView { export class WaveTabView extends WebContentsView {
isActiveTab: boolean; isActiveTab: boolean;
waveWindowId: string; // set when showing in an active window waveWindowId: string; // set when showing in an active window
waveTabId: string; // always set, WaveTabViews are unique per tab private _waveTabId: string; // always set, WaveTabViews are unique per tab
lastUsedTs: number; // ts milliseconds lastUsedTs: number; // ts milliseconds
createdTs: number; // ts milliseconds createdTs: number; // ts milliseconds
initPromise: Promise<void>; initPromise: Promise<void>;
initResolve: () => void;
savedInitOpts: WaveInitOpts; savedInitOpts: WaveInitOpts;
waveReadyPromise: Promise<void>; waveReadyPromise: Promise<void>;
initResolve: () => void;
waveReadyResolve: () => void; waveReadyResolve: () => void;
// used to destroy the tab if it is not initialized within a certain time after being assigned a tabId
private destroyTabTimeout: NodeJS.Timeout;
constructor(fullConfig: FullConfigType) { constructor(fullConfig: FullConfigType) {
console.log("createBareTabView"); console.log("createBareTabView");
super({ super({
@ -60,6 +63,13 @@ export class WaveTabView extends WebContentsView {
this.waveReadyPromise = new Promise((resolve, _) => { this.waveReadyPromise = new Promise((resolve, _) => {
this.waveReadyResolve = resolve; this.waveReadyResolve = resolve;
}); });
// Once the frontend is ready, we can cancel the destroyTabTimeout, assuming the tab hasn't been destroyed yet
// Only after a tab is ready will we add it to the wcvCache
this.waveReadyPromise.then(() => {
clearTimeout(this.destroyTabTimeout);
setWaveTabView(this.waveTabId, this);
});
wcIdToWaveTabMap.set(this.webContents.id, this); wcIdToWaveTabMap.set(this.webContents.id, this);
if (isDevVite) { if (isDevVite) {
this.webContents.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html}`); this.webContents.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html}`);
@ -73,6 +83,17 @@ export class WaveTabView extends WebContentsView {
this.setBackgroundColor(computeBgColor(fullConfig)); this.setBackgroundColor(computeBgColor(fullConfig));
} }
get waveTabId(): string {
return this._waveTabId;
}
set waveTabId(waveTabId: string) {
this._waveTabId = waveTabId;
this.destroyTabTimeout = setTimeout(() => {
this.destroy();
}, 1000);
}
positionTabOnScreen(winBounds: Rectangle) { positionTabOnScreen(winBounds: Rectangle) {
const curBounds = this.getBounds(); const curBounds = this.getBounds();
if ( if (
@ -102,7 +123,7 @@ export class WaveTabView extends WebContentsView {
destroy() { destroy() {
console.log("destroy tab", this.waveTabId); console.log("destroy tab", this.waveTabId);
this.webContents.close(); this.webContents?.close();
removeWaveTabView(this.waveTabId); removeWaveTabView(this.waveTabId);
// TODO: circuitous // TODO: circuitous
@ -171,7 +192,6 @@ export function getOrCreateWebViewForTab(fullConfig: FullConfigType, tabId: stri
tabView = getSpareTab(fullConfig); tabView = getSpareTab(fullConfig);
tabView.lastUsedTs = Date.now(); tabView.lastUsedTs = Date.now();
tabView.waveTabId = tabId; tabView.waveTabId = tabId;
setWaveTabView(tabId, tabView);
tabView.webContents.on("will-navigate", shNavHandler); tabView.webContents.on("will-navigate", shNavHandler);
tabView.webContents.on("will-frame-navigate", shFrameNavHandler); tabView.webContents.on("will-frame-navigate", shFrameNavHandler);
tabView.webContents.on("did-attach-webview", (event, wc) => { tabView.webContents.on("did-attach-webview", (event, wc) => {