From 962283051886d59307d489b67787ae04cbb7a324 Mon Sep 17 00:00:00 2001 From: Jeff Miller Date: Thu, 7 Dec 2023 16:10:22 -0500 Subject: [PATCH] Disable "Show Local" button, add --disable-file-uploads option and add file operation trace level logs (#6557) --- patches/display-language.diff | 12 +- ...nloads.diff => external-file-actions.diff} | 212 ++++++++++++++++-- patches/getting-started.diff | 36 +-- patches/series | 2 +- src/node/cli.ts | 5 + test/e2e/downloads.test.ts | 68 +++++- test/e2e/models/CodeServer.ts | 12 +- test/e2e/uploads.test.ts | 61 +++++ 8 files changed, 369 insertions(+), 39 deletions(-) rename patches/{disable-downloads.diff => external-file-actions.diff} (50%) create mode 100644 test/e2e/uploads.test.ts diff --git a/patches/display-language.diff b/patches/display-language.diff index 5bade0167..1762849b2 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -220,7 +220,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts import { CharCode } from 'vs/base/common/charCode'; import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -@@ -344,6 +345,8 @@ export class WebClientServer { +@@ -345,6 +346,8 @@ export class WebClientServer { callbackRoute: this._callbackRoute }; @@ -229,7 +229,7 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; const values: { [key: string]: string } = { WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), -@@ -352,6 +355,7 @@ export class WebClientServer { +@@ -353,6 +356,7 @@ export class WebClientServer { WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), BASE: base, VS_BASE: vscodeBase, @@ -241,22 +241,26 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts -@@ -17,6 +17,7 @@ export const serverOptions: OptionDescri +@@ -17,8 +17,9 @@ export const serverOptions: OptionDescri 'disable-update-check': { type: 'boolean' }, 'auth': { type: 'string' }, 'disable-file-downloads': { type: 'boolean' }, + 'disable-file-uploads': { type: 'boolean' }, + 'locale': { type: 'string' }, /* ----- server setup ----- */ -@@ -99,6 +100,7 @@ export interface ServerParsedArgs { + 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, +@@ -100,8 +100,9 @@ export interface ServerParsedArgs { 'disable-update-check'?: boolean; 'auth'?: string 'disable-file-downloads'?: boolean; + 'disable-file-uploads'?: boolean; + 'locale'?: string /* ----- server setup ----- */ + host?: string; Index: code-server/lib/vscode/src/vs/workbench/workbench.web.main.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/workbench.web.main.ts diff --git a/patches/disable-downloads.diff b/patches/external-file-actions.diff similarity index 50% rename from patches/disable-downloads.diff rename to patches/external-file-actions.diff index e60802b77..655e8ee6b 100644 --- a/patches/disable-downloads.diff +++ b/patches/external-file-actions.diff @@ -1,18 +1,33 @@ -Add option to disable file downloads via CLI +Add option to disable file downloads and uploads via cli This patch adds support for a new CLI flag called `--disable-file-downloads` which allows a user to remove the "Download..." option that shows up when you -right-click files in Code. The default value for this is `false`. +right-click files in Code. It also disables the "Show Local" button on the dialog +for Save, Save-As and Save Workspace. The default value for this is `false`. -To test this, start code-server with `--disable-file-downloads`, open editor, +This patch also add support for a new CLI flag called `--disable-file-uploads` +which disables the drag to upload functionality and the "Upload..." option when you +right-click folders in Code. It also disables the "Show Local" button on the dialog +for opening a file. The default value for this is `false`. + +This patch also adds trace log statements for when a file is read and written to disk. + +To test disabling downloads, start code-server with `--disable-file-downloads`, open editor, right-click on a file (not a folder) and you should **not** see the -"Download..." option. +"Download..." option. When saving a file or workspace, the "Show Local" button +should **not** appear on the dialog that comes on screen. + +To test disabling uploads, start code-server with `--disable-file-uploads`, open editor, +right-click on a folder (not a file) and you should **not** see the +"Upload..." option. If you drag a file into the file navigator, the file should **not** upload +and appear in the file navigator. When opening a file, the "Show Local" button +should **not** appear on the dialog that comes on screen. Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts -@@ -281,6 +281,11 @@ export interface IWorkbenchConstructionO +@@ -281,6 +281,16 @@ export interface IWorkbenchConstructionO */ readonly userDataPath?: string @@ -20,6 +35,11 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts + * Whether the "Download..." option is enabled for files. + */ + readonly isEnabledFileDownloads?: boolean ++ ++ /** ++ * Whether the "Upload..." button is enabled. ++ */ ++ readonly isEnabledFileUploads?: boolean + //#endregion @@ -28,7 +48,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts -@@ -34,6 +34,11 @@ export interface IBrowserWorkbenchEnviro +@@ -34,6 +34,16 @@ export interface IBrowserWorkbenchEnviro readonly options?: IWorkbenchConstructionOptions; /** @@ -36,11 +56,16 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi + */ + readonly isEnabledFileDownloads?: boolean; + ++ /** ++ * Enable uploading files via menu actions. ++ */ ++ readonly isEnabledFileUploads?: boolean; ++ + /** * Gets whether a resolver extension is expected for the environment. */ readonly expectsResolverExtension: boolean; -@@ -111,6 +116,13 @@ export class BrowserWorkbenchEnvironment +@@ -111,6 +121,20 @@ export class BrowserWorkbenchEnvironment return this.options.userDataPath; } @@ -50,6 +75,13 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi + } + return this.options.isEnabledFileDownloads; + } ++ ++ get isEnabledFileUploads(): boolean { ++ if (typeof this.options.isEnabledFileUploads === "undefined") { ++ throw new Error('isEnabledFileUploads was not provided to the browser'); ++ } ++ return this.options.isEnabledFileUploads; ++ } + @memoize get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } @@ -58,19 +90,21 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts -@@ -16,6 +16,7 @@ export const serverOptions: OptionDescri +@@ -16,6 +16,8 @@ export const serverOptions: OptionDescri /* ----- code-server ----- */ 'disable-update-check': { type: 'boolean' }, 'auth': { type: 'string' }, + 'disable-file-downloads': { type: 'boolean' }, ++ 'disable-file-uploads': { type: 'boolean' }, /* ----- server setup ----- */ -@@ -97,6 +98,7 @@ export interface ServerParsedArgs { +@@ -97,6 +99,8 @@ export interface ServerParsedArgs { /* ----- code-server ----- */ 'disable-update-check'?: boolean; 'auth'?: string + 'disable-file-downloads'?: boolean; ++ 'disable-file-uploads'?: boolean; /* ----- server setup ----- */ @@ -78,14 +112,17 @@ Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts -@@ -332,6 +332,7 @@ export class WebClientServer { +@@ -331,8 +331,10 @@ export class WebClientServer { + const workbenchWebConfiguration = { remoteAuthority, webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', userDataPath: this._environmentService.userDataPath, + isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'], ++ isEnabledFileUploads: !this._environmentService.args['disable-file-uploads'], _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, + enableWorkspaceTrust: !this._environmentService.args['disable-workspace-trust'], Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts @@ -95,7 +132,7 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; -+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; ++import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds ,MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext, IsEnabledFileDownloads, IsEnabledFileUploads } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -114,12 +151,13 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts @IProductService private readonly productService: IProductService, @IEditorService private readonly editorService: IEditorService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, -@@ -219,6 +219,9 @@ export class WorkbenchContextKeysHandler +@@ -219,6 +219,10 @@ export class WorkbenchContextKeysHandler this.auxiliaryBarVisibleContext = AuxiliaryBarVisibleContext.bindTo(this.contextKeyService); this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART)); + // code-server + IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true) ++ IsEnabledFileUploads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileUploads ?? true) + this.registerListeners(); } @@ -133,7 +171,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/contextkeys'; -+import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, IsEnabledFileDownloads } from 'vs/workbench/common/contextkeys'; ++import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, IsEnabledFileDownloads, IsEnabledFileUploads } from 'vs/workbench/common/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ThemeIcon } from 'vs/base/common/themables'; @@ -161,16 +199,162 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/fileActions ) })); +@@ -567,6 +570,7 @@ MenuRegistry.appendMenuItem(MenuId.Explo + title: UPLOAD_LABEL, + }, + when: ContextKeyExpr.and( ++ IsEnabledFileUploads, + // only in web + IsWebContext, + // only on folders Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts +++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts -@@ -39,6 +39,8 @@ export const HasWebFileSystemAccess = ne +@@ -39,6 +39,9 @@ export const HasWebFileSystemAccess = ne export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); +export const IsEnabledFileDownloads = new RawContextKey('isEnabledFileDownloads', true, true); ++export const IsEnabledFileUploads = new RawContextKey('isEnabledFileUploads', true, true); + //#endregion +Index: code-server/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts ++++ code-server/lib/vscode/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +@@ -18,7 +18,7 @@ import { IModelService } from 'vs/editor + import { ILanguageService } from 'vs/editor/common/languages/language'; + import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; + import { Schemas } from 'vs/base/common/network'; +-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; ++import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; + import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; + import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; +@@ -142,7 +142,7 @@ export class SimpleFileDialog implements + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService, +- @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, ++ @IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IPathService protected readonly pathService: IPathService, + @IKeybindingService private readonly keybindingService: IKeybindingService, +@@ -286,20 +286,22 @@ export class SimpleFileDialog implements + this.filePickBox.autoFocusOnList = false; + this.filePickBox.ignoreFocusOut = true; + this.filePickBox.ok = true; +- if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) { +- this.filePickBox.customButton = true; +- this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local'); +- let action; +- if (isSave) { +- action = SaveLocalFileCommand; +- } else { +- action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand; +- } +- const keybinding = this.keybindingService.lookupKeybinding(action.ID); +- if (keybinding) { +- const label = keybinding.getLabel(); +- if (label) { +- this.filePickBox.customHover = format('{0} ({1})', action.LABEL, label); ++ if ((isSave && this.environmentService.isEnabledFileDownloads) || (!isSave && this.environmentService.isEnabledFileUploads)) { ++ if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) { ++ this.filePickBox.customButton = true; ++ this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local'); ++ let action; ++ if (isSave) { ++ action = SaveLocalFileCommand; ++ } else { ++ action = this.allowFileSelection ? (this.allowFolderSelection ? OpenLocalFileFolderCommand : OpenLocalFileCommand) : OpenLocalFolderCommand; ++ } ++ const keybinding = this.keybindingService.lookupKeybinding(action.ID); ++ if (keybinding) { ++ const label = keybinding.getLabel(); ++ if (label) { ++ this.filePickBox.customHover = format('{0} ({1})', action.LABEL, label); ++ } + } + } + } +Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts ++++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +@@ -66,6 +66,7 @@ import { IHoverDelegate, IHoverDelegateO + import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; + import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; + import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; ++import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; + + export class ExplorerDelegate implements IListVirtualDelegate { + +@@ -1053,7 +1054,8 @@ export class FileDragAndDrop implements + @IConfigurationService private configurationService: IConfigurationService, + @IInstantiationService private instantiationService: IInstantiationService, + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, +- @IUriIdentityService private readonly uriIdentityService: IUriIdentityService ++ @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, ++ @IBrowserWorkbenchEnvironmentService protected readonly environmentService: IBrowserWorkbenchEnvironmentService + ) { + const updateDropEnablement = (e: IConfigurationChangeEvent | undefined) => { + if (!e || e.affectsConfiguration('explorer.enableDragAndDrop')) { +@@ -1257,15 +1259,17 @@ export class FileDragAndDrop implements + + // External file DND (Import/Upload file) + if (data instanceof NativeDragAndDropData) { +- // Use local file import when supported +- if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(window))) { +- const fileImport = this.instantiationService.createInstance(ExternalFileImport); +- await fileImport.import(resolvedTarget, originalEvent); +- } +- // Otherwise fallback to browser based file upload +- else { +- const browserUpload = this.instantiationService.createInstance(BrowserFileUpload); +- await browserUpload.upload(target, originalEvent); ++ if (this.environmentService.isEnabledFileUploads) { ++ // Use local file import when supported ++ if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(window))) { ++ const fileImport = this.instantiationService.createInstance(ExternalFileImport); ++ await fileImport.import(resolvedTarget, originalEvent); ++ } ++ // Otherwise fallback to browser based file upload ++ else { ++ const browserUpload = this.instantiationService.createInstance(BrowserFileUpload); ++ await browserUpload.upload(target, originalEvent); ++ } + } + } + +Index: code-server/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts +=================================================================== +--- code-server.orig/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts ++++ code-server/lib/vscode/src/vs/platform/files/node/diskFileSystemProviderServer.ts +@@ -92,6 +92,7 @@ export abstract class AbstractDiskFileSy + + private async readFile(uriTransformer: IURITransformer, _resource: UriComponents, opts?: IFileAtomicReadOptions): Promise { + const resource = this.transformIncoming(uriTransformer, _resource, true); ++ this.logService.trace(`File action: readFile ${resource.path}`); + const buffer = await this.provider.readFile(resource, opts); + + return VSBuffer.wrap(buffer); +@@ -110,6 +111,7 @@ export abstract class AbstractDiskFileSy + } + }); + ++ this.logService.trace(`File action: readFileStream ${resource.path}`); + const fileStream = this.provider.readFileStream(resource, opts, cts.token); + listenStream(fileStream, { + onData: chunk => emitter.fire(VSBuffer.wrap(chunk)), +@@ -130,7 +132,7 @@ export abstract class AbstractDiskFileSy + + private writeFile(uriTransformer: IURITransformer, _resource: UriComponents, content: VSBuffer, opts: IFileWriteOptions): Promise { + const resource = this.transformIncoming(uriTransformer, _resource); +- ++ this.logService.trace(`File action: writeFile ${resource.path}`); + return this.provider.writeFile(resource, content.buffer, opts); + } + diff --git a/patches/getting-started.diff b/patches/getting-started.diff index 31a91d38d..b5adfc144 100644 --- a/patches/getting-started.diff +++ b/patches/getting-started.diff @@ -135,9 +135,9 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts +++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts -@@ -286,6 +286,11 @@ export interface IWorkbenchConstructionO +@@ -291,6 +291,11 @@ export interface IWorkbenchConstructionO */ - readonly isEnabledFileDownloads?: boolean + readonly isEnabledFileUploads?: boolean + /** + * Whether to use Coder's custom Getting Started text. @@ -151,8 +151,8 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts +++ code-server/lib/vscode/src/vs/workbench/services/environment/browser/environmentService.ts -@@ -39,6 +39,11 @@ export interface IBrowserWorkbenchEnviro - readonly isEnabledFileDownloads?: boolean; +@@ -44,6 +44,11 @@ export interface IBrowserWorkbenchEnviro + readonly isEnabledFileUploads?: boolean; /** + * Enable Coder's custom getting started text. @@ -163,8 +163,8 @@ Index: code-server/lib/vscode/src/vs/workbench/services/environment/browser/envi * Gets whether a resolver extension is expected for the environment. */ readonly expectsResolverExtension: boolean; -@@ -123,6 +128,13 @@ export class BrowserWorkbenchEnvironment - return this.options.isEnabledFileDownloads; +@@ -135,6 +140,13 @@ export class BrowserWorkbenchEnvironment + return this.options.isEnabledFileUploads; } + get isEnabledCoderGettingStarted(): boolean { @@ -181,34 +181,40 @@ Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts +++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts -@@ -18,6 +18,7 @@ export const serverOptions: OptionDescri +@@ -18,8 +18,9 @@ export const serverOptions: OptionDescri 'auth': { type: 'string' }, 'disable-file-downloads': { type: 'boolean' }, + 'disable-file-uploads': { type: 'boolean' }, 'locale': { type: 'string' }, + 'disable-getting-started-override': { type: 'boolean' }, /* ----- server setup ----- */ -@@ -101,6 +102,7 @@ export interface ServerParsedArgs { + 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, +@@ -102,8 +103,9 @@ export interface ServerParsedArgs { 'auth'?: string 'disable-file-downloads'?: boolean; + 'disable-file-uploads'?: boolean; 'locale'?: string + 'disable-getting-started-override'?: boolean, /* ----- server setup ----- */ + host?: string; Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts +++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts -@@ -335,6 +335,7 @@ export class WebClientServer { +@@ -335,8 +335,9 @@ export class WebClientServer { webviewEndpoint: vscodeBase + this._staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', userDataPath: this._environmentService.userDataPath, isEnabledFileDownloads: !this._environmentService.args['disable-file-downloads'], + isEnabledFileUploads: !this._environmentService.args['disable-file-uploads'], + isEnabledCoderGettingStarted: !this._environmentService.args['disable-getting-started-override'], _wrapWebWorkerExtHostInIframe, developmentOptions: { enableSmokeTestDriver: this._environmentService.args['enable-smoke-test-driver'] ? true : undefined, logLevel: this._logService.getLevel() }, settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined, + enableWorkspaceTrust: !this._environmentService.args['disable-workspace-trust'], Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/browser/contextkeys.ts @@ -217,28 +223,32 @@ Index: code-server/lib/vscode/src/vs/workbench/browser/contextkeys.ts import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; --import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; -+import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, IsEnabledFileDownloads, IsEnabledCoderGettingStarted, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; +-import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds ,MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext, IsEnabledFileDownloads, IsEnabledFileUploads } from 'vs/workbench/common/contextkeys'; ++import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext, IsEnabledFileDownloads, IsEnabledFileUploads, IsEnabledCoderGettingStarted, } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -@@ -221,6 +221,7 @@ export class WorkbenchContextKeysHandler +@@ -221,8 +221,9 @@ export class WorkbenchContextKeysHandler // code-server IsEnabledFileDownloads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileDownloads ?? true) + IsEnabledFileUploads.bindTo(this.contextKeyService).set(this.environmentService.isEnabledFileUploads ?? true) + IsEnabledCoderGettingStarted.bindTo(this.contextKeyService).set(this.environmentService.isEnabledCoderGettingStarted ?? true) this.registerListeners(); } + Index: code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts =================================================================== --- code-server.orig/lib/vscode/src/vs/workbench/common/contextkeys.ts +++ code-server/lib/vscode/src/vs/workbench/common/contextkeys.ts -@@ -40,6 +40,7 @@ export const HasWebFileSystemAccess = ne +@@ -40,8 +40,9 @@ export const HasWebFileSystemAccess = ne export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); export const IsEnabledFileDownloads = new RawContextKey('isEnabledFileDownloads', true, true); + export const IsEnabledFileUploads = new RawContextKey('isEnabledFileUploads', true, true); +export const IsEnabledCoderGettingStarted = new RawContextKey('isEnabledCoderGettingStarted', true, true); //#endregion + diff --git a/patches/series b/patches/series index 898d2974a..d3370af12 100644 --- a/patches/series +++ b/patches/series @@ -13,7 +13,7 @@ unique-db.diff local-storage.diff service-worker.diff sourcemaps.diff -disable-downloads.diff +external-file-actions.diff telemetry.diff display-language.diff cli-window-open.diff diff --git a/src/node/cli.ts b/src/node/cli.ts index ff80c8872..7566bd9a0 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -48,6 +48,7 @@ export interface UserProvidedCodeArgs { "github-auth"?: string "disable-update-check"?: boolean "disable-file-downloads"?: boolean + "disable-file-uploads"?: boolean "disable-workspace-trust"?: boolean "disable-getting-started-override"?: boolean "disable-proxy"?: boolean @@ -170,6 +171,10 @@ export const options: Options> = { description: "Disable file downloads from Code. This can also be set with CS_DISABLE_FILE_DOWNLOADS set to 'true' or '1'.", }, + "disable-file-uploads": { + type: "boolean", + description: "Disable file uploads.", + }, "disable-workspace-trust": { type: "boolean", description: "Disable Workspace Trust feature. This switch only affects the current session.", diff --git a/test/e2e/downloads.test.ts b/test/e2e/downloads.test.ts index f155f2387..401cc4227 100644 --- a/test/e2e/downloads.test.ts +++ b/test/e2e/downloads.test.ts @@ -23,6 +23,41 @@ describe("Downloads (enabled)", ["--disable-workspace-trust"], {}, async () => { expect(await codeServerPage.page.isVisible("text=Download...")).toBe(true) }) + + test("should see the 'Show Local' button on Save As", async ({ codeServerPage }) => { + // Setup + const workspaceDir = await codeServerPage.workspaceDir + const fileName = "unique-file-save-as.txt" + const tmpFilePath = path.join(workspaceDir, fileName) + await fs.writeFile(tmpFilePath, "Hello World") + + // Action + await codeServerPage.page.waitForSelector(`text=${fileName}`) + + await codeServerPage.openFile(fileName) + await codeServerPage.page.click(".tab") + await codeServerPage.navigateMenus(["File", "Auto Save"]) + await codeServerPage.page.keyboard.type("Making some edits.") + await codeServerPage.navigateMenus(["File", "Save As..."]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(true) + }) + + test("should see the 'Show Local' button on Save File", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "New Text File"]) + await codeServerPage.waitForTab("Untitled-1") + await codeServerPage.navigateMenus(["File", "Save"]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(true) + }) + + test("should see the 'Show Local' button on Save Workspace As", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "Save Workspace As..."]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(true) + }) }) describe("Downloads (disabled)", ["--disable-workspace-trust", "--disable-file-downloads"], {}, async () => { @@ -35,7 +70,7 @@ describe("Downloads (disabled)", ["--disable-workspace-trust", "--disable-file-d // Setup const workspaceDir = await codeServerPage.workspaceDir const tmpFilePath = path.join(workspaceDir, "unique-file.txt") - await fs.writeFile(tmpFilePath, "hello world") + await fs.writeFile(tmpFilePath, "Hello World") // Action const fileInExplorer = await codeServerPage.page.waitForSelector("text=unique-file.txt") @@ -45,4 +80,35 @@ describe("Downloads (disabled)", ["--disable-workspace-trust", "--disable-file-d expect(await codeServerPage.page.isVisible("text=Download...")).toBe(false) }) + + test("should not see the 'Show Local' button on Save as", async ({ codeServerPage }) => { + // Setup + const workspaceDir = await codeServerPage.workspaceDir + const fileName = "unique-file-save-as.txt" + const tmpFilePath = path.join(workspaceDir, fileName) + await fs.writeFile(tmpFilePath, "Hello World") + + // Action + await codeServerPage.page.waitForSelector(`text=${fileName}`) + await codeServerPage.openFile(fileName) + await codeServerPage.page.click(".tab") + await codeServerPage.navigateMenus(["File", "Save As..."]) + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(false) + }) + + test("should not see the 'Show Local' button on Save File", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "New Text File"]) + await codeServerPage.waitForTab("Untitled-1") + await codeServerPage.navigateMenus(["File", "Save"]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(false) + }) + + test("should not see the 'Show Local' button on Save Workspace As", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "Save Workspace As..."]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(false) + }) }) diff --git a/test/e2e/models/CodeServer.ts b/test/e2e/models/CodeServer.ts index b4f62f331..14555035b 100644 --- a/test/e2e/models/CodeServer.ts +++ b/test/e2e/models/CodeServer.ts @@ -401,7 +401,7 @@ export class CodeServerPage { * Open a file by using menus. */ async openFile(file: string) { - await this.navigateMenus(["File", "Open File"]) + await this.navigateMenus(["File", "Open File..."]) await this.navigateQuickInput([path.basename(file)]) await this.waitForTab(file) } @@ -432,7 +432,7 @@ export class CodeServerPage { * it then clicking the match from the results. */ async executeCommandViaMenus(command: string) { - await this.navigateMenus(["View", "Command Palette"]) + await this.navigateMenus(["View", "Command Palette..."]) await this.page.keyboard.type(command) @@ -488,19 +488,19 @@ export class CodeServerPage { // splitting them into two steps each we can cancel before running the // action. steps.push({ - fn: () => this.page.hover(`${selector} :text("${item}")`, { trial: true }), + fn: () => this.page.hover(`${selector} :text-is("${item}")`, { trial: true }), name: `${item}:hover:trial`, }) steps.push({ - fn: () => this.page.hover(`${selector} :text("${item}")`, { force: true }), + fn: () => this.page.hover(`${selector} :text-is("${item}")`, { force: true }), name: `${item}:hover:force`, }) steps.push({ - fn: () => this.page.click(`${selector} :text("${item}")`, { trial: true }), + fn: () => this.page.click(`${selector} :text-is("${item}")`, { trial: true }), name: `${item}:click:trial`, }) steps.push({ - fn: () => this.page.click(`${selector} :text("${item}")`, { force: true }), + fn: () => this.page.click(`${selector} :text-is("${item}")`, { force: true }), name: `${item}:click:force`, }) } diff --git a/test/e2e/uploads.test.ts b/test/e2e/uploads.test.ts new file mode 100644 index 000000000..55ebea947 --- /dev/null +++ b/test/e2e/uploads.test.ts @@ -0,0 +1,61 @@ +import { promises as fs } from "fs" +import * as path from "path" +import { clean } from "../utils/helpers" +import { describe, test, expect } from "./baseFixture" + +describe("Uploads (enabled)", ["--disable-workspace-trust"], {}, () => { + const testName = "uploads-enabled" + test.beforeAll(async () => { + await clean(testName) + }) + + test("should see the 'Upload...' option", async ({ codeServerPage }) => { + // Setup + const workspaceDir = await codeServerPage.workspaceDir + const tmpDirPath = path.join(workspaceDir, "test-directory") + await fs.mkdir(tmpDirPath) + + // Action + const fileInExplorer = await codeServerPage.page.waitForSelector('span:has-text("test-directory")') + await fileInExplorer.click({ + button: "right", + }) + expect(await codeServerPage.page.isVisible("text=Upload...")).toBe(true) + }) + + test("should see the 'Show Local' button on Open File", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "Open File..."]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(true) + }) +}) + +describe("Uploads (disabled)", ["--disable-workspace-trust", "--disable-file-uploads"], {}, () => { + const testName = "uploads-disabled" + test.beforeAll(async () => { + await clean(testName) + }) + + test("should not see the 'Upload...' option", async ({ codeServerPage }) => { + // Setup + const workspaceDir = await codeServerPage.workspaceDir + const tmpDirPath = path.join(workspaceDir, "test-directory") + await fs.mkdir(tmpDirPath) + + // Action + const fileInExplorer = await codeServerPage.page.waitForSelector('span:has-text("test-directory")') + await fileInExplorer.click({ + button: "right", + }) + + expect(await codeServerPage.page.isVisible("text=Upload...")).toBe(false) + }) + + test("should not see the 'Show Local' button on Open File", async ({ codeServerPage }) => { + // Action + await codeServerPage.navigateMenus(["File", "Open File..."]) + await codeServerPage.page.waitForSelector(".quick-input-widget") + expect(await codeServerPage.page.isVisible("text=Show Local")).toBe(false) + }) +})