Skip to content

Commit 0a9a979

Browse files
authored
Merge pull request #2891 from github/koesie10/dispose-webview
Dispose tracked objects when panel is disposed
2 parents 66fdabf + 4cf67ef commit 0a9a979

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

extensions/ql-vscode/src/common/vscode/abstract-webview.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { join } from "path";
1010

1111
import { App } from "../app";
12-
import { DisposableObject, DisposeHandler } from "../disposable-object";
12+
import { Disposable } from "../disposable-object";
1313
import { tmpDir } from "../../tmp-dir";
1414
import { getHtmlForWebview, WebviewMessage, WebviewKind } from "./webview-html";
1515

@@ -27,16 +27,16 @@ export type WebviewPanelConfig = {
2727
export abstract class AbstractWebview<
2828
ToMessage extends WebviewMessage,
2929
FromMessage extends WebviewMessage,
30-
> extends DisposableObject {
30+
> {
3131
protected panel: WebviewPanel | undefined;
3232
protected panelLoaded = false;
3333
protected panelLoadedCallBacks: Array<() => void> = [];
3434

3535
private panelResolves?: Array<(panel: WebviewPanel) => void>;
3636

37-
constructor(protected readonly app: App) {
38-
super();
39-
}
37+
private disposables: Disposable[] = [];
38+
39+
constructor(protected readonly app: App) {}
4040

4141
public async restoreView(panel: WebviewPanel): Promise<void> {
4242
this.panel = panel;
@@ -101,6 +101,7 @@ export abstract class AbstractWebview<
101101
this.panel = undefined;
102102
this.panelLoaded = false;
103103
this.onPanelDispose();
104+
this.disposeAll();
104105
}, null),
105106
);
106107

@@ -150,8 +151,27 @@ export abstract class AbstractWebview<
150151
return panel.webview.postMessage(msg);
151152
}
152153

153-
public dispose(disposeHandler?: DisposeHandler) {
154+
public dispose() {
154155
this.panel?.dispose();
155-
super.dispose(disposeHandler);
156+
this.disposeAll();
157+
}
158+
159+
private disposeAll() {
160+
while (this.disposables.length > 0) {
161+
const disposable = this.disposables.pop()!;
162+
disposable.dispose();
163+
}
164+
}
165+
166+
/**
167+
* Adds `obj` to a list of objects to dispose when the panel is disposed. Objects added by `push` are
168+
* disposed in reverse order of being added.
169+
* @param obj The object to take ownership of.
170+
*/
171+
protected push<T extends Disposable>(obj: T): T {
172+
if (obj !== undefined) {
173+
this.disposables.push(obj);
174+
}
175+
return obj;
156176
}
157177
}

extensions/ql-vscode/src/local-queries/results-view.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import { telemetryListener } from "../common/vscode/telemetry";
7575
import { redactableError } from "../common/errors";
7676
import { ResultsViewCommands } from "../common/commands";
7777
import { App } from "../common/app";
78+
import { Disposable } from "../common/disposable-object";
7879

7980
/**
8081
* results-view.ts
@@ -157,6 +158,12 @@ function numInterpretedPages(
157158
return Math.ceil(n / pageSize);
158159
}
159160

161+
/**
162+
* The results view is used for displaying the results of a local query. It is a singleton; only 1 results view exists
163+
* in the extension. It is created when the extension is activated and disposed of when the extension is deactivated.
164+
* There can be multiple panels linked to this view over the lifetime of the extension, but there is only ever 1 panel
165+
* active at a time.
166+
*/
160167
export class ResultsView extends AbstractWebview<
161168
IntoResultsViewMsg,
162169
FromResultsViewMsg
@@ -168,6 +175,9 @@ export class ResultsView extends AbstractWebview<
168175
"codeql-query-results",
169176
);
170177

178+
// Event listeners that should be disposed of when the view is disposed.
179+
private disposableEventListeners: Disposable[] = [];
180+
171181
constructor(
172182
app: App,
173183
private databaseManager: DatabaseManager,
@@ -176,14 +186,16 @@ export class ResultsView extends AbstractWebview<
176186
private labelProvider: HistoryItemLabelProvider,
177187
) {
178188
super(app);
179-
this.push(this._diagnosticCollection);
180-
this.push(
189+
190+
// We can't use this.push for these two event listeners because they need to be disposed of when the view is
191+
// disposed, not when the panel is disposed. The results view is a singleton, so we shouldn't be calling this.push.
192+
this.disposableEventListeners.push(
181193
vscode.window.onDidChangeTextEditorSelection(
182194
this.handleSelectionChange.bind(this),
183195
),
184196
);
185197

186-
this.push(
198+
this.disposableEventListeners.push(
187199
this.databaseManager.onDidChangeDatabaseItem(({ kind }) => {
188200
if (kind === DatabaseEventKind.Remove) {
189201
this._diagnosticCollection.clear();
@@ -981,4 +993,12 @@ export class ResultsView extends AbstractWebview<
981993
editor.setDecorations(shownLocationLineDecoration, []);
982994
}
983995
}
996+
997+
dispose() {
998+
super.dispose();
999+
1000+
this._diagnosticCollection.dispose();
1001+
this.disposableEventListeners.forEach((d) => d.dispose());
1002+
this.disposableEventListeners = [];
1003+
}
9841004
}

0 commit comments

Comments
 (0)