mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
Portable mode improvements and bug fixes (#287063)
Disabled protocol handlers and registry updates on Windows in portable mode. Added API proposal to detect if VS Code is running in portable mode from extensions. Skipped protocol redirect in GitHub authentication in portable mode.
This commit is contained in:
@@ -46,7 +46,12 @@
|
||||
if (error) {
|
||||
document.querySelector('.error-message > .detail').textContent = error;
|
||||
document.querySelector('body').classList.add('error');
|
||||
} else if (redirectUri) {
|
||||
} else if (!redirectUri) {
|
||||
// Portable mode: authentication succeeded, no redirect needed
|
||||
document.querySelector('.title').textContent = appName;
|
||||
document.querySelector('.success-message > .subtitle').textContent = 'You have successfully signed in.';
|
||||
document.querySelector('.success-message > .detail').textContent = 'You can now close this window.';
|
||||
} else {
|
||||
// Wrap the redirect URI so that the browser remembers who triggered the redirect
|
||||
const wrappedRedirectUri = `https://vscode.dev/redirect?url=${encodeURIComponent(redirectUri)}`;
|
||||
// Set up the fallback link
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"authIssuers",
|
||||
"authProviderSpecific"
|
||||
"authProviderSpecific",
|
||||
"envIsAppPortable"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"capabilities": {
|
||||
|
||||
@@ -354,7 +354,7 @@ class LocalServerFlow implements IFlow {
|
||||
path: '/login/oauth/authorize',
|
||||
query: searchParams.toString()
|
||||
});
|
||||
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true));
|
||||
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true), callbackUri.toString(true), env.isAppPortable);
|
||||
const port = await server.start();
|
||||
|
||||
let codeToExchange;
|
||||
|
||||
@@ -87,7 +87,7 @@ export class LoopbackAuthServer implements ILoopbackServer {
|
||||
return this._startingRedirect.searchParams.get('state') ?? undefined;
|
||||
}
|
||||
|
||||
constructor(serveRoot: string, startingRedirect: string, callbackUri: string) {
|
||||
constructor(serveRoot: string, startingRedirect: string, callbackUri: string, isPortable: boolean) {
|
||||
if (!serveRoot) {
|
||||
throw new Error('serveRoot must be defined');
|
||||
}
|
||||
@@ -132,7 +132,11 @@ export class LoopbackAuthServer implements ILoopbackServer {
|
||||
throw new Error('Nonce does not match.');
|
||||
}
|
||||
deferred.resolve({ code, state });
|
||||
res.writeHead(302, { location: `/?redirect_uri=${encodeURIComponent(callbackUri)}${appNameQueryParam}` });
|
||||
if (isPortable) {
|
||||
res.writeHead(302, { location: `/?app_name=${encodeURIComponent(env.appName)}` });
|
||||
} else {
|
||||
res.writeHead(302, { location: `/?redirect_uri=${encodeURIComponent(callbackUri)}${appNameQueryParam}` });
|
||||
}
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ suite('LoopbackAuthServer', () => {
|
||||
let port: number;
|
||||
|
||||
setup(async () => {
|
||||
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com');
|
||||
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com', false);
|
||||
port = await server.start();
|
||||
});
|
||||
|
||||
@@ -64,3 +64,35 @@ suite('LoopbackAuthServer', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('LoopbackAuthServer (portable mode)', () => {
|
||||
let server: LoopbackAuthServer;
|
||||
let port: number;
|
||||
|
||||
setup(async () => {
|
||||
server = new LoopbackAuthServer(__dirname, 'http://localhost:8080', 'https://code.visualstudio.com', true);
|
||||
port = await server.start();
|
||||
});
|
||||
|
||||
teardown(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
test('should redirect to success page without redirect_uri on /callback', async () => {
|
||||
server.state = 'valid-state';
|
||||
const response = await fetch(
|
||||
`http://localhost:${port}/callback?code=valid-code&state=${server.state}&nonce=${server.nonce}`,
|
||||
{ redirect: 'manual' }
|
||||
);
|
||||
assert.strictEqual(response.status, 302);
|
||||
// In portable mode, should redirect to success page without redirect_uri
|
||||
assert.strictEqual(response.headers.get('location'), `/?app_name=${encodeURIComponent(env.appName)}`);
|
||||
await Promise.race([
|
||||
server.waitForOAuthResponse().then(result => {
|
||||
assert.strictEqual(result.code, 'valid-code');
|
||||
assert.strictEqual(result.state, server.state);
|
||||
}),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"src/**/*",
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authIssuers.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authProviderSpecific.d.ts"
|
||||
"../../src/vscode-dts/vscode.proposed.authProviderSpecific.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.envIsAppPortable.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"enabledApiProposals": [
|
||||
"nativeWindowHandle",
|
||||
"authIssuers",
|
||||
"authenticationChallenges"
|
||||
"authenticationChallenges",
|
||||
"envIsAppPortable"
|
||||
],
|
||||
"capabilities": {
|
||||
"virtualWorkspaces": true,
|
||||
|
||||
@@ -228,7 +228,8 @@ export class MsalAuthProvider implements AuthenticationProvider {
|
||||
const flows = getMsalFlows({
|
||||
extensionHost: this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote,
|
||||
supportedClient: isSupportedClient(callbackUri),
|
||||
isBrokerSupported: cachedPca.isBrokerAvailable
|
||||
isBrokerSupported: cachedPca.isBrokerAvailable,
|
||||
isPortableMode: env.isAppPortable
|
||||
});
|
||||
|
||||
const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString();
|
||||
@@ -364,7 +365,8 @@ export class MsalAuthProvider implements AuthenticationProvider {
|
||||
const flows = getMsalFlows({
|
||||
extensionHost: this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote,
|
||||
isBrokerSupported: cachedPca.isBrokerAvailable,
|
||||
supportedClient: isSupportedClient(callbackUri)
|
||||
supportedClient: isSupportedClient(callbackUri),
|
||||
isPortableMode: env.isAppPortable
|
||||
});
|
||||
|
||||
const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString();
|
||||
|
||||
@@ -22,6 +22,7 @@ interface IMsalFlowOptions {
|
||||
supportsRemoteExtensionHost: boolean;
|
||||
supportsUnsupportedClient: boolean;
|
||||
supportsBroker: boolean;
|
||||
supportsPortableMode: boolean;
|
||||
}
|
||||
|
||||
interface IMsalFlowTriggerOptions {
|
||||
@@ -47,7 +48,8 @@ class DefaultLoopbackFlow implements IMsalFlow {
|
||||
options: IMsalFlowOptions = {
|
||||
supportsRemoteExtensionHost: false,
|
||||
supportsUnsupportedClient: true,
|
||||
supportsBroker: true
|
||||
supportsBroker: true,
|
||||
supportsPortableMode: true
|
||||
};
|
||||
|
||||
async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
||||
@@ -76,7 +78,8 @@ class UrlHandlerFlow implements IMsalFlow {
|
||||
options: IMsalFlowOptions = {
|
||||
supportsRemoteExtensionHost: true,
|
||||
supportsUnsupportedClient: false,
|
||||
supportsBroker: false
|
||||
supportsBroker: false,
|
||||
supportsPortableMode: false
|
||||
};
|
||||
|
||||
async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger, uriHandler, callbackUri }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
||||
@@ -105,7 +108,8 @@ class DeviceCodeFlow implements IMsalFlow {
|
||||
options: IMsalFlowOptions = {
|
||||
supportsRemoteExtensionHost: true,
|
||||
supportsUnsupportedClient: true,
|
||||
supportsBroker: false
|
||||
supportsBroker: false,
|
||||
supportsPortableMode: true
|
||||
};
|
||||
|
||||
async trigger({ cachedPca, authority, scopes, claims, logger }: IMsalFlowTriggerOptions): Promise<AuthenticationResult> {
|
||||
@@ -128,6 +132,7 @@ export interface IMsalFlowQuery {
|
||||
extensionHost: ExtensionHost;
|
||||
supportedClient: boolean;
|
||||
isBrokerSupported: boolean;
|
||||
isPortableMode: boolean;
|
||||
}
|
||||
|
||||
export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] {
|
||||
@@ -139,6 +144,7 @@ export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] {
|
||||
}
|
||||
useFlow &&= flow.options.supportsBroker || !query.isBrokerSupported;
|
||||
useFlow &&= flow.options.supportsUnsupportedClient || query.supportedClient;
|
||||
useFlow &&= flow.options.supportsPortableMode || !query.isPortableMode;
|
||||
if (useFlow) {
|
||||
flows.push(flow);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Local,
|
||||
supportedClient: true,
|
||||
isBrokerSupported: false
|
||||
isBrokerSupported: false,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 3);
|
||||
@@ -24,7 +25,8 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Local,
|
||||
supportedClient: true,
|
||||
isBrokerSupported: true
|
||||
isBrokerSupported: true,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 1);
|
||||
@@ -35,7 +37,8 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Remote,
|
||||
supportedClient: true,
|
||||
isBrokerSupported: false
|
||||
isBrokerSupported: false,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 2);
|
||||
@@ -47,7 +50,8 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Local,
|
||||
supportedClient: false,
|
||||
isBrokerSupported: false
|
||||
isBrokerSupported: false,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 2);
|
||||
@@ -59,7 +63,8 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Remote,
|
||||
supportedClient: false,
|
||||
isBrokerSupported: false
|
||||
isBrokerSupported: false,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 1);
|
||||
@@ -70,10 +75,24 @@ suite('getMsalFlows', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Local,
|
||||
supportedClient: false,
|
||||
isBrokerSupported: true
|
||||
isBrokerSupported: true,
|
||||
isPortableMode: false
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 1);
|
||||
assert.strictEqual(flows[0].label, 'default');
|
||||
});
|
||||
|
||||
test('should exclude protocol handler flow in portable mode', () => {
|
||||
const query: IMsalFlowQuery = {
|
||||
extensionHost: ExtensionHost.Local,
|
||||
supportedClient: true,
|
||||
isBrokerSupported: false,
|
||||
isPortableMode: true
|
||||
};
|
||||
const flows = getMsalFlows(query);
|
||||
assert.strictEqual(flows.length, 2);
|
||||
assert.strictEqual(flows[0].label, 'default');
|
||||
assert.strictEqual(flows[1].label, 'device code');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"../../src/vscode-dts/vscode.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authIssuers.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts"
|
||||
"../../src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.envIsAppPortable.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"documentFiltersExclusive",
|
||||
"editorInsets",
|
||||
"embeddings",
|
||||
"envIsAppPortable",
|
||||
"extensionRuntime",
|
||||
"extensionsAny",
|
||||
"externalUriOpener",
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
|
||||
|
||||
// --- config
|
||||
readonly disableUpdates: boolean;
|
||||
readonly isPortable: boolean;
|
||||
|
||||
// TODO@deepak1556 temporary until a real fix lands upstream
|
||||
readonly enableRDPDisplayTracking: boolean;
|
||||
@@ -56,6 +57,9 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
|
||||
@memoize
|
||||
get disableUpdates(): boolean { return !!this.args['disable-updates']; }
|
||||
|
||||
@memoize
|
||||
get isPortable(): boolean { return !!process.env['VSCODE_PORTABLE']; }
|
||||
|
||||
@memoize
|
||||
get crossOriginIsolated(): boolean { return !!this.args['enable-coi']; }
|
||||
|
||||
|
||||
@@ -225,6 +225,9 @@ const _allApiProposals = {
|
||||
embeddings: {
|
||||
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.embeddings.d.ts',
|
||||
},
|
||||
envIsAppPortable: {
|
||||
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.envIsAppPortable.d.ts',
|
||||
},
|
||||
extensionAffinity: {
|
||||
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.extensionAffinity.d.ts',
|
||||
},
|
||||
|
||||
@@ -49,7 +49,9 @@ export class ElectronURLListener extends Disposable {
|
||||
}
|
||||
|
||||
// Windows: install as protocol handler
|
||||
if (isWindows) {
|
||||
// Skip in portable mode: the registered command wouldn't preserve
|
||||
// portable mode settings, causing issues with OAuth flows
|
||||
if (isWindows && !environmentMainService.isPortable) {
|
||||
const windowsParameters = environmentMainService.isBuilt ? [] : [`"${environmentMainService.appRoot}"`];
|
||||
windowsParameters.push('--open-url', '--');
|
||||
app.setAsDefaultProtocolClient(productService.urlProtocol, process.execPath, windowsParameters);
|
||||
|
||||
@@ -428,6 +428,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
|
||||
machineId: string;
|
||||
sqmId: string;
|
||||
devDeviceId: string;
|
||||
isPortable: boolean;
|
||||
|
||||
execPath: string;
|
||||
backupPath?: string;
|
||||
|
||||
@@ -1483,6 +1483,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
machineId: this.machineId,
|
||||
sqmId: this.sqmId,
|
||||
devDeviceId: this.devDeviceId,
|
||||
isPortable: this.environmentMainService.isPortable,
|
||||
|
||||
windowId: -1, // Will be filled in by the window once loaded later
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { IWorkspaceIdentifier, WORKSPACE_EXTENSION } from '../../workspace/commo
|
||||
import { IWorkspacesManagementMainService } from './workspacesManagementMainService.js';
|
||||
import { ResourceMap } from '../../../base/common/map.js';
|
||||
import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js';
|
||||
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
|
||||
|
||||
export const IWorkspacesHistoryMainService = createDecorator<IWorkspacesHistoryMainService>('workspacesHistoryMainService');
|
||||
|
||||
@@ -56,7 +57,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService,
|
||||
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -104,7 +106,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
files.push(recent);
|
||||
|
||||
// Add to recent documents (Windows only, macOS later)
|
||||
if (isWindows && recent.fileUri.scheme === Schemas.file) {
|
||||
// Skip in portable mode to avoid leaving traces on the machine
|
||||
if (isWindows && recent.fileUri.scheme === Schemas.file && !this.environmentMainService.isPortable) {
|
||||
app.addRecentDocument(recent.fileUri.fsPath);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +130,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
this._onDidChangeRecentlyOpened.fire();
|
||||
|
||||
// Schedule update to recent documents on macOS dock
|
||||
if (isMacintosh) {
|
||||
// Skip in portable mode to avoid leaving traces on the machine
|
||||
if (isMacintosh && !this.environmentMainService.isPortable) {
|
||||
this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments());
|
||||
}
|
||||
}
|
||||
@@ -153,7 +157,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
this._onDidChangeRecentlyOpened.fire();
|
||||
|
||||
// Schedule update to recent documents on macOS dock
|
||||
if (isMacintosh) {
|
||||
// Skip in portable mode to avoid leaving traces on the machine
|
||||
if (isMacintosh && !this.environmentMainService.isPortable) {
|
||||
this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments());
|
||||
}
|
||||
}
|
||||
@@ -178,7 +183,11 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
}
|
||||
|
||||
await this.saveRecentlyOpened({ workspaces: [], files: [] });
|
||||
app.clearRecentDocuments();
|
||||
|
||||
// Skip in portable mode to avoid leaving traces on the machine
|
||||
if (!this.environmentMainService.isPortable) {
|
||||
app.clearRecentDocuments();
|
||||
}
|
||||
|
||||
// Event
|
||||
this._onDidChangeRecentlyOpened.fire();
|
||||
@@ -311,6 +320,11 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
|
||||
return; // only on windows
|
||||
}
|
||||
|
||||
// Skip in portable mode to avoid leaving traces on the machine
|
||||
if (this.environmentMainService.isPortable) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.updateWindowsJumpList();
|
||||
this._register(this.onDidChangeRecentlyOpened(() => this.updateWindowsJumpList()));
|
||||
}
|
||||
|
||||
@@ -391,6 +391,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension, 'devDeviceId');
|
||||
return initData.telemetryInfo.devDeviceId ?? initData.telemetryInfo.machineId;
|
||||
},
|
||||
get isAppPortable() {
|
||||
checkProposedApiEnabled(extension, 'envIsAppPortable');
|
||||
return initData.environment.isPortable ?? false;
|
||||
},
|
||||
get sessionId() { return initData.telemetryInfo.sessionId; },
|
||||
get language() { return initData.environment.appLanguage; },
|
||||
get appName() { return initData.environment.appName; },
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface INativeWorkbenchEnvironmentService extends IBrowserWorkbenchEnv
|
||||
readonly machineId: string;
|
||||
readonly sqmId: string;
|
||||
readonly devDeviceId: string;
|
||||
readonly isPortable: boolean;
|
||||
|
||||
// --- Paths
|
||||
readonly execPath: string;
|
||||
@@ -70,6 +71,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
|
||||
@memoize
|
||||
get devDeviceId() { return this.configuration.devDeviceId; }
|
||||
|
||||
@memoize
|
||||
get isPortable() { return this.configuration.isPortable; }
|
||||
|
||||
@memoize
|
||||
get remoteAuthority() { return this.configuration.remoteAuthority; }
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
|
||||
appUriScheme: this._productService.urlProtocol,
|
||||
appLanguage: platform.language,
|
||||
isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),
|
||||
isPortable: false,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome,
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface IEnvironment {
|
||||
appLanguage: string;
|
||||
isExtensionTelemetryLoggingOnly: boolean;
|
||||
appUriScheme: string;
|
||||
isPortable?: boolean;
|
||||
extensionDevelopmentLocationURI?: URI[];
|
||||
extensionTestsLocationURI?: URI;
|
||||
globalStorageHome: URI;
|
||||
|
||||
@@ -482,6 +482,7 @@ export class NativeLocalProcessExtensionHost extends Disposable implements IExte
|
||||
appHost: this._productService.embedderIdentifier || 'desktop',
|
||||
appUriScheme: this._productService.urlProtocol,
|
||||
isExtensionTelemetryLoggingOnly: isLoggingOnly(this._productService, this._environmentService),
|
||||
isPortable: this._environmentService.isPortable,
|
||||
appLanguage: platform.language,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
|
||||
@@ -59,6 +59,7 @@ const TestNativeWindowConfiguration: INativeWindowConfiguration = {
|
||||
machineId: 'testMachineId',
|
||||
sqmId: 'testSqmId',
|
||||
devDeviceId: 'testdevDeviceId',
|
||||
isPortable: false,
|
||||
logLevel: LogLevel.Error,
|
||||
loggers: [],
|
||||
mainPid: 0,
|
||||
|
||||
20
src/vscode-dts/vscode.proposed.envIsAppPortable.d.ts
vendored
Normal file
20
src/vscode-dts/vscode.proposed.envIsAppPortable.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
export namespace env {
|
||||
|
||||
/**
|
||||
* Indicates whether the application is running in portable mode.
|
||||
*
|
||||
* Portable mode is enabled when the application is run from a folder that contains
|
||||
* a `data` directory, allowing for self-contained installations.
|
||||
*
|
||||
* Learn more about [Portable Mode](https://code.visualstudio.com/docs/editor/portable).
|
||||
*/
|
||||
export const isAppPortable: boolean;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user