Skip to content

Commit 7cc3d44

Browse files
jriekendeepak1556
andauthored
ployfill navigator on NodeJS - but to undefined (microsoft#250619)
* ployfill `navigator` on NodeJS - but to `undefined` This is used so that we can learn about extensions probing for `navigator` on NodeJS. With latest version this will pass and likely be surprising because navigator historically means browser, not NodeJS * chore: polyfill behind a setting * chore: fix tests * chore: update doc * chore: log err stack in development --------- Co-authored-by: deepak1556 <hop2deep@gmail.com>
1 parent 7c985e9 commit 7cc3d44

File tree

7 files changed

+83
-8
lines changed

7 files changed

+83
-8
lines changed

src/vs/base/common/errors.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ export class CancellationError extends Error {
213213
}
214214
}
215215

216+
export class PendingMigrationError extends Error {
217+
218+
private static readonly _name = 'PendingMigrationError';
219+
220+
static is(error: unknown): error is PendingMigrationError {
221+
return error instanceof PendingMigrationError || (error instanceof Error && error.name === PendingMigrationError._name);
222+
}
223+
224+
constructor(message: string) {
225+
super(message);
226+
this.name = PendingMigrationError._name;
227+
}
228+
}
229+
216230
/**
217231
* @deprecated use {@link CancellationError `new CancellationError()`} instead
218232
*/

src/vs/platform/extensions/electron-main/extensionHostStarter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ILogService } from '../../log/common/log.js';
1313
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
1414
import { WindowUtilityProcess } from '../../utilityProcess/electron-main/utilityProcess.js';
1515
import { IWindowsMainService } from '../../windows/electron-main/windows.js';
16+
import { IConfigurationService } from '../../configuration/common/configuration.js';
1617

1718
export class ExtensionHostStarter extends Disposable implements IDisposable, IExtensionHostStarter {
1819

@@ -28,6 +29,7 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx
2829
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
2930
@IWindowsMainService private readonly _windowsMainService: IWindowsMainService,
3031
@ITelemetryService private readonly _telemetryService: ITelemetryService,
32+
@IConfigurationService private readonly _configurationService: IConfigurationService,
3133
) {
3234
super();
3335

@@ -105,12 +107,16 @@ export class ExtensionHostStarter extends Disposable implements IDisposable, IEx
105107
throw canceled();
106108
}
107109
const extHost = this._getExtHost(id);
110+
const args = ['--skipWorkspaceStorageLock'];
111+
if (this._configurationService.getValue<boolean>('extensions.supportNodeGlobalNavigator')) {
112+
args.push('--supportGlobalNavigator');
113+
}
108114
extHost.start({
109115
...opts,
110116
type: 'extensionHost',
111117
name: 'extension-host',
112118
entryPoint: 'vs/workbench/api/node/extensionHostProcess',
113-
args: ['--skipWorkspaceStorageLock'],
119+
args,
114120
execArgv: opts.execArgv,
115121
allowLoadingUnsignedLibraries: true,
116122
respondToAuthRequestsFromMainProcess: true,

src/vs/server/node/extensionHostConnection.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ export class ExtensionHostConnection extends Disposable {
271271
const args = ['--type=extensionHost', `--transformURIs`];
272272
const useHostProxy = this._environmentService.args['use-host-proxy'];
273273
args.push(`--useHostProxy=${useHostProxy ? 'true' : 'false'}`);
274+
if (this._configurationService.getValue<boolean>('extensions.supportNodeGlobalNavigator')) {
275+
args.push('--supportGlobalNavigator');
276+
}
274277
this._extensionHostProcess = cp.fork(FileAccess.asFileUri('bootstrap-fork').fsPath, args, opts);
275278
const pid = this._extensionHostProcess.pid;
276279
this._log(`<${pid}> Launched Extension Host Process.`);

src/vs/workbench/api/common/extensionHostMain.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { IURITransformerService, URITransformerService } from './extHostUriTrans
2323
import { IExtHostExtensionService, IHostUtils } from './extHostExtensionService.js';
2424
import { IExtHostTelemetry } from './extHostTelemetry.js';
2525
import { Mutable } from '../../../base/common/types.js';
26+
import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js';
2627

2728
export interface IExitFn {
2829
(code?: number): any;
@@ -59,11 +60,13 @@ export abstract class ErrorHandler {
5960
const rpcService = accessor.get(IExtHostRpcService);
6061
const extensionService = accessor.get(IExtHostExtensionService);
6162
const extensionTelemetry = accessor.get(IExtHostTelemetry);
63+
const apiDeprecationService = accessor.get(IExtHostApiDeprecationService);
6264

6365
const mainThreadExtensions = rpcService.getProxy(MainContext.MainThreadExtensionService);
6466
const mainThreadErrors = rpcService.getProxy(MainContext.MainThreadErrors);
6567

66-
const map = await extensionService.getExtensionPathIndex();
68+
const extensionsRegistry = await extensionService.getExtensionRegistry();
69+
const extensionsMap = await extensionService.getExtensionPathIndex();
6770
const extensionErrors = new WeakMap<Error, { extensionIdentifier: ExtensionIdentifier | undefined; stack: string }>();
6871

6972
// PART 1
@@ -80,7 +83,7 @@ export abstract class ErrorHandler {
8083
stackTraceMessage += `\n\tat ${call.toString()}`;
8184
fileName = call.getFileName();
8285
if (!extension && fileName) {
83-
extension = map.findSubstr(URI.file(fileName));
86+
extension = extensionsMap.findSubstr(URI.file(fileName));
8487
}
8588
}
8689
const result = `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`;
@@ -116,7 +119,10 @@ export abstract class ErrorHandler {
116119
// having caused the error. Note that the runtime order is actually reversed, the code
117120
// below accesses the stack-property which triggers the code above
118121
errors.setUnexpectedErrorHandler(err => {
119-
logService.error(err);
122+
123+
if (!errors.PendingMigrationError.is(err)) {
124+
logService.error(err);
125+
}
120126

121127
const errorData = errors.transformErrorForSerialization(err);
122128

@@ -128,7 +134,18 @@ export abstract class ErrorHandler {
128134
extension = stackData?.extensionIdentifier;
129135
}
130136

131-
if (extension) {
137+
if (!extension) {
138+
return;
139+
}
140+
141+
if (errors.PendingMigrationError.is(err)) {
142+
// report pending migration via the API deprecation service which (1) informs the extensions author during
143+
// dev-time and (2) collects telemetry so that we can reach out too
144+
const extensionDesc = extensionsRegistry.getExtensionDescription(extension);
145+
if (extensionDesc) {
146+
apiDeprecationService.report(err.name, extensionDesc, `${err.message}\n FROM: ${err.stack}`);
147+
}
148+
} else {
132149
mainThreadExtensions.$onExtensionRuntimeError(extension, errorData);
133150
const reported = extensionTelemetry.onExtensionError(extension, err);
134151
logService.trace('forwarded error to extension?', reported, extension);

src/vs/workbench/api/node/extensionHostProcess.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as nativeWatchdog from 'native-watchdog';
88
import * as net from 'net';
99
import { ProcessTimeRunOnceScheduler } from '../../../base/common/async.js';
1010
import { VSBuffer } from '../../../base/common/buffer.js';
11-
import { isCancellationError, isSigPipeError, onUnexpectedError } from '../../../base/common/errors.js';
11+
import { PendingMigrationError, isCancellationError, isSigPipeError, onUnexpectedError, onUnexpectedExternalError } from '../../../base/common/errors.js';
1212
import { Event } from '../../../base/common/event.js';
1313
import * as performance from '../../../base/common/performance.js';
1414
import { IURITransformer } from '../../../base/common/uriIpc.js';
@@ -34,6 +34,7 @@ const require = createRequire(import.meta.url);
3434
interface ParsedExtHostArgs {
3535
transformURIs?: boolean;
3636
skipWorkspaceStorageLock?: boolean;
37+
supportGlobalNavigator?: boolean; // enable global navigator object in nodejs
3738
useHostProxy?: 'true' | 'false'; // use a string, as undefined is also a valid value
3839
}
3940

@@ -51,7 +52,8 @@ interface ParsedExtHostArgs {
5152
const args = minimist(process.argv.slice(2), {
5253
boolean: [
5354
'transformURIs',
54-
'skipWorkspaceStorageLock'
55+
'skipWorkspaceStorageLock',
56+
'supportGlobalNavigator',
5557
],
5658
string: [
5759
'useHostProxy' // 'true' | 'false' | undefined
@@ -120,6 +122,18 @@ function patchProcess(allowExit: boolean) {
120122

121123
}
122124

125+
// NodeJS since v21 defines navigator as a global object. This will likely surprise many extensions and potentially break them
126+
// because `navigator` has historically often been used to check if running in a browser (vs running inside NodeJS)
127+
if (!args.supportGlobalNavigator) {
128+
Object.defineProperty(globalThis, 'navigator', {
129+
get: () => {
130+
onUnexpectedExternalError(new PendingMigrationError('navigator is now a global in nodejs, please see https://aka.ms/vscode-extensions/navigator for additional info on this error.'));
131+
return undefined;
132+
}
133+
});
134+
}
135+
136+
123137
interface IRendererConnection {
124138
protocol: IMessagePassingProtocol;
125139
initData: IExtensionHostInitData;

src/vs/workbench/api/test/common/extensionHostMain.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';
2121
import { ErrorHandler } from '../../common/extensionHostMain.js';
2222
import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';
2323
import { ProxyIdentifier, Proxied } from '../../../services/extensions/common/proxyIdentifier.js';
24+
import { IExtHostApiDeprecationService, NullApiDeprecationService } from '../../common/extHostApiDeprecationService.js';
25+
import { ExtensionDescriptionRegistry, IActivationEventsReader } from '../../../services/extensions/common/extensionDescriptionRegistry.js';
2426

2527

2628
suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slowdown and eventual stack overflow #184926 ', function () {
@@ -39,6 +41,12 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo
3941
}
4042
};
4143

44+
const basicActivationEventsReader: IActivationEventsReader = {
45+
readActivationEvents: (extensionDescription: IExtensionDescription): string[] => {
46+
return [];
47+
}
48+
};
49+
4250
const collection = new ServiceCollection(
4351
[ILogService, new NullLogService()],
4452
[IExtHostTelemetry, new class extends mock<IExtHostTelemetry>() {
@@ -58,13 +66,21 @@ suite('ExtensionHostMain#ErrorHandler - Wrapping prepareStackTrace can cause slo
5866

5967
}(extensionsIndex);
6068
}
69+
getExtensionRegistry() {
70+
return new class extends ExtensionDescriptionRegistry {
71+
override getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined {
72+
return nullExtensionDescription;
73+
}
74+
}(basicActivationEventsReader, []);
75+
}
6176
}],
6277
[IExtHostRpcService, new class extends mock<IExtHostRpcService>() {
6378
declare readonly _serviceBrand: undefined;
6479
override getProxy<T>(identifier: ProxyIdentifier<T>): Proxied<T> {
6580
return <any>mainThreadExtensionsService;
6681
}
67-
}]
82+
}],
83+
[IExtHostApiDeprecationService, NullApiDeprecationService],
6884
);
6985

7086
const originalPrepareStackTrace = Error.prepareStackTrace;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
288288
minimumVersion: '1.99',
289289
},
290290
},
291+
'extensions.supportNodeGlobalNavigator': {
292+
type: 'boolean',
293+
description: localize('extensionsSupportNodeGlobalNavigator', "When enabled, Node.js navigator object is exposed on the global scope."),
294+
default: false,
295+
},
291296
}
292297
});
293298

0 commit comments

Comments
 (0)