Skip to content

Commit 6a44c35

Browse files
authored
Confirm opening notebooks in serverless web sessions and skip accepting context for the installExtension command (#319705)
Confirm opening notebooks in serverless web sessions and skip accepting context for the installExtension command (#319700)
1 parent 2afd9e1 commit 6a44c35

2 files changed

Lines changed: 69 additions & 12 deletions

File tree

src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import { IAction } from '../../../../base/common/actions.js';
77
import { CancellationToken } from '../../../../base/common/cancellation.js';
8-
import { IStringDictionary } from '../../../../base/common/collections.js';
98
import { onUnexpectedError } from '../../../../base/common/errors.js';
109
import { Event } from '../../../../base/common/event.js';
1110
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
@@ -409,10 +408,6 @@ CommandsRegistry.registerCommand({
409408
'type': 'boolean',
410409
'description': localize('workbench.extensions.installExtension.option.enable', "When enabled, the extension will be enabled if it is installed but disabled. If the extension is already enabled, this has no effect."),
411410
default: false
412-
},
413-
'context': {
414-
'type': 'object',
415-
'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."),
416411
}
417412
}
418413
}
@@ -428,7 +423,6 @@ CommandsRegistry.registerCommand({
428423
donotSync?: boolean;
429424
justification?: string | { reason: string; action: string };
430425
enable?: boolean;
431-
context?: IStringDictionary<any>;
432426
}) => {
433427
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
434428
const extensionManagementService = accessor.get(IWorkbenchExtensionManagementService);
@@ -446,13 +440,13 @@ CommandsRegistry.registerCommand({
446440
isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */
447441
installPreReleaseVersion: options?.installPreReleaseVersion,
448442
installGivenVersion: !!version,
449-
context: { ...options?.context, [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND },
443+
context: { [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND },
450444
});
451445
} else {
452446
await extensionsWorkbenchService.install(id, {
453447
version,
454448
installPreReleaseVersion: options?.installPreReleaseVersion,
455-
context: { ...options?.context, [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND },
449+
context: { [EXTENSION_INSTALL_SOURCE_CONTEXT]: ExtensionInstallSource.COMMAND },
456450
justification: options?.justification,
457451
enable: options?.enable,
458452
isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */

src/vs/workbench/contrib/notebook/browser/notebookEditor.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar
88
import { IAction, toAction } from '../../../../base/common/actions.js';
99
import { timeout } from '../../../../base/common/async.js';
1010
import { CancellationToken } from '../../../../base/common/cancellation.js';
11+
import { isWeb } from '../../../../base/common/platform.js';
1112
import { Emitter, Event } from '../../../../base/common/event.js';
1213
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
1314
import { extname, isEqual } from '../../../../base/common/resources.js';
@@ -16,10 +17,11 @@ import { generateUuid } from '../../../../base/common/uuid.js';
1617
import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';
1718
import { localize } from '../../../../nls.js';
1819
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
20+
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
1921
import { IEditorOptions } from '../../../../platform/editor/common/editor.js';
2022
import { ByteSize, FileOperationError, FileOperationResult, IFileService, TooLargeFileOperationError } from '../../../../platform/files/common/files.js';
2123
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
22-
import { IStorageService } from '../../../../platform/storage/common/storage.js';
24+
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
2325
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2426
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
2527
import { Selection } from '../../../../editor/common/core/selection.js';
@@ -37,6 +39,7 @@ import { NotebookEditorInput } from '../common/notebookEditorInput.js';
3739
import { NotebookPerfMarks } from '../common/notebookPerformance.js';
3840
import { GroupsOrder, IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
3941
import { IEditorService } from '../../../services/editor/common/editorService.js';
42+
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
4043
import { IEditorProgressService } from '../../../../platform/progress/common/progress.js';
4144
import { InstallRecommendedExtensionAction } from '../../extensions/browser/extensionsActions.js';
4245
import { INotebookService } from '../common/notebookService.js';
@@ -51,6 +54,15 @@ import { StopWatch } from '../../../../base/common/stopwatch.js';
5154
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
5255

5356
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
57+
const NOTEBOOK_WEB_HOST_OPEN_CONFIRMED_KEY = 'notebook.webHost.openConfirmed';
58+
59+
/**
60+
* Notebook resources that have already been confirmed for opening in a serverless web
61+
* session. This prevents re-prompting when the user switches back to an already-open
62+
* notebook, while still gating the first open of each notebook.
63+
*/
64+
const confirmedWebHostNotebooks = new Set<string>();
65+
5466

5567
export class NotebookEditor extends EditorPane implements INotebookEditorPane, IEditorPaneWithScrolling {
5668
static readonly ID: string = NOTEBOOK_EDITOR_ID;
@@ -84,7 +96,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane, I
8496
@ITelemetryService telemetryService: ITelemetryService,
8597
@IThemeService themeService: IThemeService,
8698
@IInstantiationService private readonly _instantiationService: IInstantiationService,
87-
@IStorageService storageService: IStorageService,
99+
@IStorageService private readonly _storageService: IStorageService,
88100
@IEditorService private readonly _editorService: IEditorService,
89101
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
90102
@INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService,
@@ -96,9 +108,11 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane, I
96108
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
97109
@IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService,
98110
@ILogService private readonly logService: ILogService,
99-
@IPreferencesService private readonly _preferencesService: IPreferencesService
111+
@IPreferencesService private readonly _preferencesService: IPreferencesService,
112+
@IDialogService private readonly _dialogService: IDialogService,
113+
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService
100114
) {
101-
super(NotebookEditor.ID, group, telemetryService, themeService, storageService);
115+
super(NotebookEditor.ID, group, telemetryService, themeService, _storageService);
102116
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
103117

104118
this._register(this._fileService.onDidChangeFileSystemProviderCapabilities(e => this._onDidChangeFileSystemProvider(e.scheme)));
@@ -194,7 +208,56 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane, I
194208
return !!value && (DOM.isAncestorOfActiveElement(value.getDomNode() || DOM.isAncestorOfActiveElement(value.getOverflowContainerDomNode())));
195209
}
196210

211+
/**
212+
* When running serverless on the web (i.e. in the browser with no remote server
213+
* connected), prompt the user to confirm that they really want to open the notebook.
214+
* The confirmation is only shown the first time a given notebook is opened in the
215+
* session (so switching back to an already-open notebook does not re-prompt), and the
216+
* choice can be remembered for the whole workspace via a "Don't ask again" checkbox.
217+
*/
218+
private async _confirmOpenOnWebHost(input: NotebookEditorInput): Promise<void> {
219+
const isServerlessWeb = isWeb && !this._environmentService.remoteAuthority;
220+
if (!isServerlessWeb) {
221+
return;
222+
}
223+
224+
if (this._storageService.getBoolean(NOTEBOOK_WEB_HOST_OPEN_CONFIRMED_KEY, StorageScope.WORKSPACE, false)) {
225+
return;
226+
}
227+
228+
const resourceKey = input.resource.toString();
229+
if (confirmedWebHostNotebooks.has(resourceKey)) {
230+
return;
231+
}
232+
233+
const { confirmed, checkboxChecked } = await this._dialogService.confirm({
234+
type: 'warning',
235+
message: localize('notebook.webHost.confirm', "Do you trust the authors of this notebook?"),
236+
detail: localize('notebook.webHost.detail', "Notebooks can run code that has access to your browser session, including any signed-in accounts. Only open notebooks from authors you trust."),
237+
primaryButton: localize('notebook.webHost.open', "Open Notebook"),
238+
checkbox: { label: localize('notebook.webHost.remember', "Don't ask me again") }
239+
});
240+
241+
if (!confirmed) {
242+
throw createEditorOpenError(localize('notebook.webHost.declined', "The notebook was not opened because its authors are not trusted."), [
243+
toAction({
244+
id: 'workbench.notebook.action.openAsText', label: localize('notebookOpenAsText', "Open As Text"), run: async () => {
245+
this._editorService.openEditor({ resource: input.resource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id, pinned: true } });
246+
}
247+
})
248+
], { forceMessage: true });
249+
}
250+
251+
confirmedWebHostNotebooks.add(resourceKey);
252+
253+
if (checkboxChecked) {
254+
this._storageService.store(NOTEBOOK_WEB_HOST_OPEN_CONFIRMED_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
255+
}
256+
}
257+
197258
override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise<void> {
259+
await this._confirmOpenOnWebHost(input);
260+
198261
try {
199262
let perfMarksCaptured = false;
200263
const fileOpenMonitor = timeout(10000);

0 commit comments

Comments
 (0)