Skip to content

Commit b893fd1

Browse files
committed
Add UnownedDisposable to make it clearer that webviewEditorInput takes ownership of the webview passed to it
1 parent 5bb6e68 commit b893fd1

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

src/vs/base/common/lifecycle.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,43 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable {
207207
}
208208
}
209209

210+
/**
211+
* Wrapper class that stores a disposable that is not currently "owned" by anyone.
212+
*
213+
* Example use cases:
214+
*
215+
* - Express that a function/method will take ownership of a disposable parameter.
216+
* - Express that a function returns a disposable that the caller must explicitly take ownership of.
217+
*/
218+
export class UnownedDisposable<T extends IDisposable> extends Disposable {
219+
private _hasBeenAcquired = false;
220+
private _value?: T;
221+
222+
public constructor(value: T) {
223+
super();
224+
this._value = value;
225+
}
226+
227+
public acquire(): T {
228+
if (this._hasBeenAcquired) {
229+
throw new Error('This disposable has already been acquired');
230+
}
231+
this._hasBeenAcquired = true;
232+
const value = this._value!;
233+
this._value = undefined;
234+
return value;
235+
}
236+
237+
public dispose() {
238+
super.dispose();
239+
if (!this._hasBeenAcquired) {
240+
this._hasBeenAcquired = true;
241+
this._value!.dispose();
242+
this._value = undefined;
243+
}
244+
}
245+
}
246+
210247
export interface IReference<T> extends IDisposable {
211248
readonly object: T;
212249
}

src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor';
99
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1010
import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor';
1111
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview';
12+
import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle';
1213

1314
class WebviewIconsManager {
1415
private readonly _icons = new Map<string, { light: URI, dark: URI }>();
@@ -58,6 +59,7 @@ export class WebviewEditorInput extends EditorInput {
5859
private _name: string;
5960
private _iconPath?: { light: URI, dark: URI };
6061
private _group?: GroupIdentifier;
62+
private readonly _webview: WebviewEditorOverlay;
6163

6264
constructor(
6365
public readonly id: string,
@@ -67,14 +69,14 @@ export class WebviewEditorInput extends EditorInput {
6769
readonly location: URI;
6870
readonly id: ExtensionIdentifier;
6971
},
70-
public readonly webview: WebviewEditorOverlay,
72+
webview: Unowned<WebviewEditorOverlay>,
7173
) {
7274
super();
7375

7476
this._name = name;
7577
this.extension = extension;
7678

77-
this._register(webview); // The input owns this webview
79+
this._webview = this._register(webview.acquire()); // The input owns this webview
7880
}
7981

8082
public getTypeId(): string {
@@ -105,6 +107,10 @@ export class WebviewEditorInput extends EditorInput {
105107
this._onDidChangeLabel.fire();
106108
}
107109

110+
public get webview() {
111+
return this._webview;
112+
}
113+
108114
public get iconPath() {
109115
return this._iconPath;
110116
}
@@ -147,7 +153,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
147153
readonly id: ExtensionIdentifier
148154
},
149155
private readonly reviver: (input: WebviewEditorInput) => Promise<void>,
150-
webview: WebviewEditorOverlay,
156+
webview: Unowned<WebviewEditorOverlay>,
151157
) {
152158
super(id, viewType, name, extension, webview);
153159
}

src/vs/workbench/contrib/webview/browser/webviewEditorService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { equals } from 'vs/base/common/arrays';
7-
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
7+
import { IDisposable, toDisposable, UnownedDisposable } from 'vs/base/common/lifecycle';
88
import { values } from 'vs/base/common/map';
99
import { URI } from 'vs/base/common/uri';
1010
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
@@ -144,7 +144,7 @@ export class WebviewEditorService implements IWebviewEditorService {
144144
): WebviewEditorInput {
145145
const webview = this.createWebiew(id, extension, options);
146146

147-
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, webview);
147+
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, new UnownedDisposable(webview));
148148
this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group);
149149
return webviewInput;
150150
}
@@ -191,7 +191,7 @@ export class WebviewEditorService implements IWebviewEditorService {
191191
const promise = new Promise<void>(r => { resolve = r; });
192192
this._revivalPool.add(webview, resolve!);
193193
return promise;
194-
}, webview);
194+
}, new UnownedDisposable(webview));
195195

196196
webviewInput.iconPath = iconPath;
197197

0 commit comments

Comments
 (0)