code cache - cleanup in prep for chrome based code loading

This commit is contained in:
Benjamin Pasero 2021-06-08 07:39:43 +02:00
parent e4373f7591
commit 2cb7b42d98
No known key found for this signature in database
GPG key ID: E6380CC4C8219E65
18 changed files with 303 additions and 294 deletions

View file

@ -43,11 +43,11 @@ exports.load = function (entrypoint, onLoad, onError) {
return;
}
// cached data config
if (process.env['VSCODE_NODE_CACHED_DATA_DIR']) {
// code cache config
if (process.env['VSCODE_CODE_CACHE_PATH']) {
loader.config({
nodeCachedData: {
path: process.env['VSCODE_NODE_CACHED_DATA_DIR'],
path: process.env['VSCODE_CODE_CACHE_PATH'],
seed: entrypoint
}
});

View file

@ -148,9 +148,9 @@
}
// Cached data config
if (configuration.nodeCachedDataDir) {
if (configuration.codeCachePath) {
loaderConfig.nodeCachedData = {
path: configuration.nodeCachedDataDir,
path: configuration.codeCachePath,
seed: modulePaths.join('')
};
}

View file

@ -40,6 +40,9 @@ const args = parseCLIArgs();
const userDataPath = getUserDataPath(args);
app.setPath('userData', userDataPath);
// Resolve code cache path
const codeCachePath = getCodeCachePath();
// Configure static command line arguments
const argvConfig = configureCommandlineSwitchesSync(args);
@ -71,9 +74,6 @@ protocol.registerSchemesAsPrivileged([
// Global app listeners
registerListeners();
// Cached data
const nodeCachedDataDir = getNodeCachedDir();
/**
* Support user defined locale: load it early before app('ready')
* to have more things running in parallel.
@ -108,14 +108,14 @@ app.once('ready', function () {
/**
* Main startup routine
*
* @param {string | undefined} cachedDataDir
* @param {string | undefined} codeCachePath
* @param {NLSConfiguration} nlsConfig
*/
function startup(cachedDataDir, nlsConfig) {
function startup(codeCachePath, nlsConfig) {
nlsConfig._languagePackSupport = true;
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
// Load main in AMD
perf.mark('code/willLoadMainBundle');
@ -128,9 +128,9 @@ async function onReady() {
perf.mark('code/mainAppReady');
try {
const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]);
startup(cachedDataDir, nlsConfig);
startup(codeCachePath, nlsConfig);
} catch (error) {
console.error(error);
}
@ -499,46 +499,28 @@ function registerListeners() {
}
/**
* @returns {{ ensureExists: () => Promise<string | undefined> }}
* @returns {string | undefined} the location to use for the code cache
* or `undefined` if disabled.
*/
function getNodeCachedDir() {
return new class {
function getCodeCachePath() {
constructor() {
this.value = this.compute();
}
// explicitly disabled via CLI args
if (process.argv.indexOf('--no-cached-data') > 0) {
return undefined;
}
async ensureExists() {
if (typeof this.value === 'string') {
try {
await mkdirp(this.value);
// running out of sources
if (process.env['VSCODE_DEV']) {
return undefined;
}
return this.value;
} catch (error) {
// ignore
}
}
}
// require commit id
const commit = product.commit;
if (!commit) {
return undefined;
}
compute() {
if (process.argv.indexOf('--no-cached-data') > 0) {
return undefined;
}
// IEnvironmentService.isBuilt
if (process.env['VSCODE_DEV']) {
return undefined;
}
// find commit id
const commit = product.commit;
if (!commit) {
return undefined;
}
return path.join(userDataPath, 'CachedData', commit);
}
};
return path.join(userDataPath, 'CachedData', commit);
}
/**
@ -553,6 +535,24 @@ function mkdirp(dir) {
});
}
/**
* @param {string | undefined} dir
* @returns {Promise<string | undefined>}
*/
async function mkdirpIgnoreError(dir) {
if (typeof dir === 'string') {
try {
await mkdirp(dir);
return dir;
} catch (error) {
// ignore
}
}
return undefined;
}
//#region NLS Support
/**

View file

@ -46,7 +46,7 @@ export interface ISandboxConfiguration {
zoomLevel?: number;
/**
* @deprecated to be removed soon
* Location of V8 code cache.
*/
nodeCachedDataDir?: string;
codeCachePath?: string;
}

View file

@ -21,7 +21,7 @@ suite('Processes', () => {
VSCODE_NLS_CONFIG: 'x',
VSCODE_PORTABLE: 'x',
VSCODE_PID: 'x',
VSCODE_NODE_CACHED_DATA_DIR: 'x',
VSCODE_CODE_CACHE_PATH: 'x',
VSCODE_NEW_VAR: 'x',
GDK_PIXBUF_MODULE_FILE: 'x',
GDK_PIXBUF_MODULEDIR: 'x',

View file

@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { basename, dirname, join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { Promises, rimraf } from 'vs/base/node/pfs';
import { IProductService } from 'vs/platform/product/common/productService';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class CodeCacheCleaner extends Disposable {
private readonly _DataMaxAge = this.productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week (insiders)
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months (stable)
constructor(
currentCodeCachePath: string | undefined,
@IProductService private readonly productService: IProductService,
@ILogService private readonly logService: ILogService
) {
super();
// Cached data is stored as user data and we run a cleanup task everytime
// the editor starts. The strategy is to delete all files that are older than
// 3 months (1 week respectively)
if (currentCodeCachePath) {
const scheduler = this._register(new RunOnceScheduler(() => {
this.cleanUpCodeCaches(currentCodeCachePath);
}, 30 * 1000 /* after 30s */));
scheduler.schedule();
}
}
private async cleanUpCodeCaches(currentCodeCachePath: string): Promise<void> {
this.logService.info('[code cache cleanup]: Starting to clean up old code cache folders.');
try {
const now = Date.now();
// The folder which contains folders of cached data.
// Each of these folders is partioned per commit
const codeCacheRootPath = dirname(currentCodeCachePath);
const currentCodeCache = basename(currentCodeCachePath);
const codeCaches = await Promises.readdir(codeCacheRootPath);
await Promise.all(codeCaches.map(async codeCache => {
if (codeCache === currentCodeCache) {
return; // not the current cache folder
}
// Delete cache folder if old enough
const codeCacheEntryPath = join(codeCacheRootPath, codeCache);
const codeCacheEntryStat = await Promises.stat(codeCacheEntryPath);
if (codeCacheEntryStat.isDirectory() && (now - codeCacheEntryStat.mtime.getTime()) > this._DataMaxAge) {
this.logService.info(`[code cache cleanup]: Removing code cache folder ${codeCache}.`);
return rimraf(codeCacheEntryPath);
}
}));
} catch (error) {
onUnexpectedError(error);
}
}
}

View file

@ -3,16 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { join } from 'vs/base/common/path';
import { Promises, rimraf } from 'vs/base/node/pfs';
import { IStringDictionary } from 'vs/base/common/collections';
import { IProductService } from 'vs/platform/product/common/productService';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { RunOnceScheduler } from 'vs/base/common/async';
interface ExtensionEntry {
interface IExtensionEntry {
version: string;
extensionIdentifier: {
id: string;
@ -20,87 +21,88 @@ interface ExtensionEntry {
};
}
interface LanguagePackEntry {
interface ILanguagePackEntry {
hash: string;
extensions: ExtensionEntry[];
extensions: IExtensionEntry[];
}
interface LanguagePackFile {
[locale: string]: LanguagePackEntry;
interface ILanguagePackFile {
[locale: string]: ILanguagePackEntry;
}
export class LanguagePackCachedDataCleaner extends Disposable {
private readonly _DataMaxAge = this._productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
private readonly _DataMaxAge = this.productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week (insiders)
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months (stable)
constructor(
@INativeEnvironmentService private readonly _environmentService: INativeEnvironmentService,
@ILogService private readonly _logService: ILogService,
@IProductService private readonly _productService: IProductService
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService,
@IProductService private readonly productService: IProductService
) {
super();
// We have no Language pack support for dev version (run from source)
// So only cleanup when we have a build version.
if (this._environmentService.isBuilt) {
this._manageCachedDataSoon();
if (this.environmentService.isBuilt) {
const scheduler = this._register(new RunOnceScheduler(() => {
this.cleanUpLanguagePackCache();
}, 40 * 1000 /* after 40s */));
scheduler.schedule();
}
}
private _manageCachedDataSoon(): void {
let handle: any = setTimeout(async () => {
handle = undefined;
this._logService.info('Starting to clean up unused language packs.');
try {
const installed: IStringDictionary<boolean> = Object.create(null);
const metaData: LanguagePackFile = JSON.parse(await pfs.Promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
for (let locale of Object.keys(metaData)) {
const entry = metaData[locale];
installed[`${entry.hash}.${locale}`] = true;
private async cleanUpLanguagePackCache(): Promise<void> {
this.logService.info('[language pack cache cleanup]: Starting to clean up unused language packs.');
try {
const installed: IStringDictionary<boolean> = Object.create(null);
const metaData: ILanguagePackFile = JSON.parse(await Promises.readFile(join(this.environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
for (let locale of Object.keys(metaData)) {
const entry = metaData[locale];
installed[`${entry.hash}.${locale}`] = true;
}
// Cleanup entries for language packs that aren't installed anymore
const cacheDir = join(this.environmentService.userDataPath, 'clp');
const cacheDirExists = await Promises.exists(cacheDir);
if (!cacheDirExists) {
return;
}
const entries = await Promises.readdir(cacheDir);
for (const entry of entries) {
if (installed[entry]) {
this.logService.info(`[language pack cache cleanup]: Skipping folder ${entry}. Language pack still in use.`);
continue;
}
// Cleanup entries for language packs that aren't installed anymore
const cacheDir = path.join(this._environmentService.userDataPath, 'clp');
const exists = await pfs.Promises.exists(cacheDir);
if (!exists) {
return;
}
for (let entry of await pfs.Promises.readdir(cacheDir)) {
if (installed[entry]) {
this._logService.info(`Skipping directory ${entry}. Language pack still in use.`);
this.logService.info(`[language pack cache cleanup]: Removing unused language pack: ${entry}`);
await rimraf(join(cacheDir, entry));
}
const now = Date.now();
for (const packEntry of Object.keys(installed)) {
const folder = join(cacheDir, packEntry);
const entries = await Promises.readdir(folder);
for (const entry of entries) {
if (entry === 'tcf.json') {
continue;
}
this._logService.info('Removing unused language pack:', entry);
await pfs.rimraf(path.join(cacheDir, entry));
}
const now = Date.now();
for (let packEntry of Object.keys(installed)) {
const folder = path.join(cacheDir, packEntry);
for (let entry of await pfs.Promises.readdir(folder)) {
if (entry === 'tcf.json') {
continue;
}
const candidate = path.join(folder, entry);
const stat = await pfs.Promises.stat(candidate);
if (stat.isDirectory()) {
const diff = now - stat.mtime.getTime();
if (diff > this._DataMaxAge) {
this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry));
await pfs.rimraf(candidate);
}
}
const candidate = join(folder, entry);
const stat = await Promises.stat(candidate);
if (stat.isDirectory() && (now - stat.mtime.getTime()) > this._DataMaxAge) {
this.logService.info(`[language pack cache cleanup]: Removing language pack cache folder: ${join(packEntry, entry)}`);
await rimraf(candidate);
}
}
} catch (error) {
onUnexpectedError(error);
}
}, 40 * 1000);
this._register(toDisposable(() => {
if (handle !== undefined) {
clearTimeout(handle);
}
}));
} catch (error) {
onUnexpectedError(error);
}
}
}

View file

@ -5,42 +5,46 @@
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { join, dirname, basename } from 'vs/base/common/path';
import { Promises as FSPromises, rimraf } from 'vs/base/node/pfs';
import { Promises, rimraf } from 'vs/base/node/pfs';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Promises } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class LogsDataCleaner extends Disposable {
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
this.cleanUpOldLogsSoon();
const scheduler = this._register(new RunOnceScheduler(() => {
this.cleanUpOldLogs();
}, 10 * 1000 /* after 10s */));
scheduler.schedule();
}
private cleanUpOldLogsSoon(): void {
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
private async cleanUpOldLogs(): Promise<void> {
this.logService.info('[logs cleanup]: Starting to clean up old logs.');
try {
const currentLog = basename(this.environmentService.logsPath);
const logsRoot = dirname(this.environmentService.logsPath);
FSPromises.readdir(logsRoot).then(children => {
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
const logFiles = await Promises.readdir(logsRoot);
return Promises.settled(toDelete.map(name => rimraf(join(logsRoot, name))));
}).then(null, onUnexpectedError);
}, 10 * 1000);
const allSessions = logFiles.filter(logFile => /^\d{8}T\d{6}$/.test(logFile));
const oldSessions = allSessions.sort().filter(session => session !== currentLog);
const sessionsToDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
this._register(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
if (sessionsToDelete.length > 0) {
this.logService.info(`[logs cleanup]: Removing log folders '${sessionsToDelete.join(', ')}'`);
await Promise.all(sessionsToDelete.map(sessionToDelete => rimraf(join(logsRoot, sessionToDelete))));
}
}));
} catch (error) {
onUnexpectedError(error);
}
}
}

View file

@ -1,86 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { basename, dirname, join } from 'vs/base/common/path';
import { onUnexpectedError } from 'vs/base/common/errors';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Promises, rimraf } from 'vs/base/node/pfs';
import { IProductService } from 'vs/platform/product/common/productService';
export class NodeCachedDataCleaner {
private readonly _DataMaxAge = this.productService.quality !== 'stable'
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
private readonly _disposables = new DisposableStore();
constructor(
private readonly nodeCachedDataDir: string | undefined,
@IProductService private readonly productService: IProductService
) {
this._manageCachedDataSoon();
}
dispose(): void {
this._disposables.dispose();
}
private _manageCachedDataSoon(): void {
// Cached data is stored as user data and we run a cleanup task everytime
// the editor starts. The strategy is to delete all files that are older than
// 3 months (1 week respectively)
if (!this.nodeCachedDataDir) {
return;
}
// The folder which contains folders of cached data. Each of these folder is per
// version
const nodeCachedDataRootDir = dirname(this.nodeCachedDataDir);
const nodeCachedDataCurrent = basename(this.nodeCachedDataDir);
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
Promises.readdir(nodeCachedDataRootDir).then(entries => {
const now = Date.now();
const deletes: Promise<unknown>[] = [];
entries.forEach(entry => {
// name check
// * not the current cached data folder
if (entry !== nodeCachedDataCurrent) {
const path = join(nodeCachedDataRootDir, entry);
deletes.push(Promises.stat(path).then(stats => {
// stat check
// * only directories
// * only when old enough
if (stats.isDirectory()) {
const diff = now - stats.mtime.getTime();
if (diff > this._DataMaxAge) {
return rimraf(path);
}
}
return undefined;
}));
}
});
return Promise.all(deletes);
}).then(undefined, onUnexpectedError);
}, 30 * 1000);
this._disposables.add(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
}
}));
}
}

View file

@ -7,8 +7,10 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
import { join } from 'vs/base/common/path';
import { Promises, rimraf } from 'vs/base/node/pfs';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup';
import { RunOnceScheduler } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class StorageDataCleaner extends Disposable {
@ -17,52 +19,44 @@ export class StorageDataCleaner extends Disposable {
constructor(
private readonly backupWorkspacesPath: string,
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
this.cleanUpStorageSoon();
const scheduler = this._register(new RunOnceScheduler(() => {
this.cleanUpStorage();
}, 30 * 1000 /* after 30s */));
scheduler.schedule();
}
private cleanUpStorageSoon(): void {
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
handle = undefined;
private async cleanUpStorage(): Promise<void> {
this.logService.info('[storage cleanup]: Starting to clean up storage folders.');
(async () => {
try {
// Leverage the backup workspace file to find out which empty workspace is currently in use to
// determine which empty workspace storage can safely be deleted
const contents = await Promises.readFile(this.backupWorkspacesPath, 'utf8');
try {
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder);
// Leverage the backup workspace file to find out which empty workspace is currently in use to
// determine which empty workspace storage can safely be deleted
const contents = await Promises.readFile(this.backupWorkspacesPath, 'utf8');
// Read all workspace storage folders that exist
const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath);
const deletes: Promise<void>[] = [];
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(emptyWorkspace => emptyWorkspace.backupFolder);
storageFolders.forEach(storageFolder => {
if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) {
return;
}
if (emptyWorkspaces.indexOf(storageFolder) === -1) {
deletes.push(rimraf(join(this.environmentService.workspaceStorageHome.fsPath, storageFolder)));
}
});
await Promise.all(deletes);
} catch (error) {
onUnexpectedError(error);
// Read all workspace storage folders that exist
const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath);
await Promise.all(storageFolders.map(async storageFolder => {
if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) {
return;
}
})();
}, 30 * 1000);
this._register(toDisposable(() => {
if (handle) {
clearTimeout(handle);
handle = undefined;
}
}));
if (emptyWorkspaces.indexOf(storageFolder) === -1) {
this.logService.info(`[storage cleanup]: Deleting storage folder ${storageFolder}.`);
await rimraf(join(this.environmentService.workspaceStorageHome.fsPath, storageFolder));
}
}));
} catch (error) {
onUnexpectedError(error);
}
}
}

View file

@ -36,7 +36,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { DownloadService } from 'vs/platform/download/common/downloadService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner';
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
@ -131,7 +131,7 @@ class SharedProcessMain extends Disposable {
// Instantiate Contributions
this._register(combinedDisposable(
instantiationService.createInstance(NodeCachedDataCleaner, this.configuration.nodeCachedDataDir),
instantiationService.createInstance(CodeCacheCleaner, this.configuration.codeCachePath),
instantiationService.createInstance(LanguagePackCachedDataCleaner),
instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath),
instantiationService.createInstance(LogsDataCleaner),

View file

@ -5,7 +5,7 @@
import { release, hostname } from 'os';
import { statSync } from 'fs';
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session, Session } from 'electron';
import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform';
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
@ -113,9 +113,60 @@ export class CodeApplication extends Disposable {
) {
super();
this.configureSession();
this.registerListeners();
}
private configureSession(): void {
//#region Security related measures (https://electronjs.org/docs/tutorial/security)
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return callback(permission === 'clipboard-read');
}
return callback(false);
});
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return permission === 'clipboard-read';
}
return false;
});
//#endregion
//#region Code Cache
type SessionWithCodeCachePathSupport = typeof Session & {
/**
* Sets code cache directory. By default, the directory will be `Code Cache` under
* the respective user data folder.
*/
setCodeCachePath?(path: string): void;
};
const defaultSession = session.defaultSession as unknown as SessionWithCodeCachePathSupport;
if (typeof defaultSession.setCodeCachePath === 'function' && this.environmentMainService.codeCachePath) {
// Make sure to partition Chrome's code cache folder
// in the same way as our code cache path to help
// invalidate caches that we know are invalid
// (https://github.com/microsoft/vscode/issues/120655)
defaultSession.setCodeCachePath(join(this.environmentMainService.codeCachePath, 'chrome'));
}
//#endregion
}
private registerListeners(): void {
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
@ -196,24 +247,6 @@ export class CodeApplication extends Disposable {
return { action: 'deny' };
});
const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return callback(permission === 'clipboard-read');
}
return callback(false);
});
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
if (isUrlFromWebview(details.requestingUrl)) {
return permission === 'clipboard-read';
}
return false;
});
});
//#endregion

View file

@ -209,7 +209,7 @@ class CodeMain {
// Environment service (paths)
Promise.all<string | undefined>([
environmentMainService.extensionsPath,
environmentMainService.nodeCachedDataDir,
environmentMainService.codeCachePath,
environmentMainService.logsPath,
environmentMainService.globalStorageHome.fsPath,
environmentMainService.workspaceStorageHome.fsPath,

View file

@ -25,11 +25,8 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
backupHome: string;
backupWorkspacesPath: string;
// --- V8 script cache path (ours)
nodeCachedDataDir?: string;
// --- V8 script cache path (chrome)
chromeCachedDataDir: string;
// --- V8 code cache path
codeCachePath?: string;
// --- IPC
mainIPCHandle: string;
@ -68,8 +65,5 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
get disableKeytar(): boolean { return !!this.args['disable-keytar']; }
@memoize
get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; }
@memoize
get chromeCachedDataDir(): string { return join(this.userDataPath, 'Code Cache'); }
get codeCachePath(): string | undefined { return process.env['VSCODE_CODE_CACHE_PATH'] || undefined; }
}

View file

@ -183,7 +183,7 @@ export class SharedProcess extends Disposable implements ISharedProcess {
machineId: this.machineId,
windowId: this.window.id,
appRoot: this.environmentMainService.appRoot,
nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir,
codeCachePath: this.environmentMainService.codeCachePath,
backupWorkspacesPath: this.environmentMainService.backupWorkspacesPath,
userEnv: this.userEnv,
args: this.environmentMainService.args,

View file

@ -206,9 +206,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
};
if (browserCodeLoadingCacheStrategy) {
this.logService.info(`window#ctor: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
this.logService.info(`window: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
} else {
this.logService.trace(`window#ctor: vscode-file:// protocol is explicitly disabled`);
this.logService.info(`window: vscode-file:// protocol is explicitly disabled`);
}
// Apply icon to window

View file

@ -1169,7 +1169,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
appRoot: this.environmentMainService.appRoot,
execPath: process.execPath,
nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir,
codeCachePath: this.environmentMainService.codeCachePath,
// If we know the backup folder upfront (for empty windows to restore), we can set it
// directly here which helps for restoring UI state associated with that window.
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before

View file

@ -180,9 +180,9 @@ export class LocalProcessExtensionHost implements IExtensionHost {
}
if (this._isExtensionDevHost) {
// Unset `VSCODE_NODE_CACHED_DATA_DIR` when developing extensions because it might
// Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might
// be that dependencies, that otherwise would be cached, get modified.
delete env['VSCODE_NODE_CACHED_DATA_DIR'];
delete env['VSCODE_CODE_CACHE_PATH'];
}
const opts = {