diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4c2d05b208e..69f1d93b2b7 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1313,7 +1313,22 @@ export function asCSSUrl(uri: URI): string { return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`; } -export function triggerDownload(uri: URI, name: string): void { + +export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void { + + // If the data is provided as Buffer, we create a + // blog URL out of it to produce a valid link + let url: string; + if (URI.isUri(dataOrUri)) { + url = dataOrUri.toString(true); + } else { + const blob = new Blob([dataOrUri]); + url = URL.createObjectURL(blob); + + // Ensure to free the data from DOM eventually + setTimeout(() => URL.revokeObjectURL(url)); + } + // In order to download from the browser, the only way seems // to be creating a element with download attribute that // points to the file to download. @@ -1321,7 +1336,7 @@ export function triggerDownload(uri: URI, name: string): void { const anchor = document.createElement('a'); document.body.appendChild(anchor); anchor.download = name; - anchor.href = uri.toString(true); + anchor.href = url; anchor.click(); // Ensure to remove the element from DOM eventually diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3bf22780290..75143ade13d 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -41,7 +41,7 @@ import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/e import { coalesce } from 'vs/base/common/arrays'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { getErrorMessage } from 'vs/base/common/errors'; -import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; +import { triggerDownload, asDomUri } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -1001,6 +1001,7 @@ export const cutFileHandler = (accessor: ServicesAccessor) => { export const DOWNLOAD_COMMAND_ID = 'explorer.download'; const downloadFileHandler = (accessor: ServicesAccessor) => { + const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); const fileDialogService = accessor.get(IFileDialogService); const explorerService = accessor.get(IExplorerService); @@ -1014,7 +1015,14 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { if (isWeb) { if (!s.isDirectory) { - triggerDownload(asDomUri(s.resource), s.name); + let bufferOrUri: Uint8Array | URI; + try { + bufferOrUri = (await fileService.readFile(s.resource, { limits: { size: 1024 * 1024 /* set a limit to reduce memory pressure */ } })).value.buffer; + } catch (error) { + bufferOrUri = asDomUri(s.resource); + } + + triggerDownload(bufferOrUri, s.name); } } else { let defaultUri = s.isDirectory ? fileDialogService.defaultFolderPath() : fileDialogService.defaultFilePath();