mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 03:29:00 +01:00
218 lines
7.7 KiB
TypeScript
218 lines
7.7 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
|
|
import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator';
|
|
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
|
|
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
|
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
|
|
import { timeout } from 'vs/base/common/async';
|
|
import { MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol';
|
|
|
|
class WorkerRequireInterceptor extends RequireInterceptor {
|
|
|
|
_installInterceptor() { }
|
|
|
|
getModule(request: string, parent: URI): undefined | any {
|
|
for (let alternativeModuleName of this._alternatives) {
|
|
let alternative = alternativeModuleName(request);
|
|
if (alternative) {
|
|
request = alternative;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this._factories.has(request)) {
|
|
return this._factories.get(request)!.load(request, parent, () => { throw new Error('CANNOT LOAD MODULE from here.'); });
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
|
|
readonly extensionRuntime = ExtensionRuntime.Webworker;
|
|
|
|
private _fakeModules?: WorkerRequireInterceptor;
|
|
|
|
protected async _beforeAlmostReadyToRunExtensions(): Promise<void> {
|
|
const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole);
|
|
wrapConsoleMethods(mainThreadConsole, this._initData.environment.isExtensionDevelopmentDebug);
|
|
|
|
// initialize API and register actors
|
|
const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors);
|
|
this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry);
|
|
await this._fakeModules.install();
|
|
performance.mark('code/extHost/didInitAPI');
|
|
|
|
await this._waitForDebuggerAttachment();
|
|
}
|
|
|
|
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {
|
|
return extensionDescription.browser;
|
|
}
|
|
|
|
protected async _loadCommonJSModule<T extends object | undefined>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
|
|
module = module.with({ path: ensureSuffix(module.path, '.js') });
|
|
if (extensionId) {
|
|
performance.mark(`code/extHost/willFetchExtensionCode/${extensionId.value}`);
|
|
}
|
|
|
|
// First resolve the extension entry point URI to something we can load using `fetch`
|
|
// This needs to be done on the main thread due to a potential `resourceUriProvider` (workbench api)
|
|
// which is only available in the main thread
|
|
const browserUri = URI.revive(await this._mainThreadExtensionsProxy.$asBrowserUri(module));
|
|
const response = await fetch(browserUri.toString(true));
|
|
if (extensionId) {
|
|
performance.mark(`code/extHost/didFetchExtensionCode/${extensionId.value}`);
|
|
}
|
|
|
|
if (response.status !== 200) {
|
|
throw new Error(response.statusText);
|
|
}
|
|
|
|
// fetch JS sources as text and create a new function around it
|
|
const source = await response.text();
|
|
// Here we append #vscode-extension to serve as a marker, such that source maps
|
|
// can be adjusted for the extra wrapping function.
|
|
const sourceURL = `${module.toString(true)}#vscode-extension`;
|
|
const fullSource = `${source}\n//# sourceURL=${sourceURL}`;
|
|
let initFn: Function;
|
|
try {
|
|
initFn = new Function('module', 'exports', 'require', fullSource);
|
|
} catch (err) {
|
|
if (extensionId) {
|
|
console.error(`Loading code for extension ${extensionId.value} failed: ${err.message}`);
|
|
} else {
|
|
console.error(`Loading code failed: ${err.message}`);
|
|
}
|
|
console.error(`${module.toString(true)}${typeof err.line === 'number' ? ` line ${err.line}` : ''}${typeof err.column === 'number' ? ` column ${err.column}` : ''}`);
|
|
console.error(err);
|
|
throw err;
|
|
}
|
|
|
|
// define commonjs globals: `module`, `exports`, and `require`
|
|
const _exports = {};
|
|
const _module = { exports: _exports };
|
|
const _require = (request: string) => {
|
|
const result = this._fakeModules!.getModule(request, module);
|
|
if (result === undefined) {
|
|
throw new Error(`Cannot load module '${request}'`);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
try {
|
|
activationTimesBuilder.codeLoadingStart();
|
|
if (extensionId) {
|
|
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
|
|
}
|
|
initFn(_module, _exports, _require);
|
|
return <T>(_module.exports !== _exports ? _module.exports : _exports);
|
|
} finally {
|
|
if (extensionId) {
|
|
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
|
|
}
|
|
activationTimesBuilder.codeLoadingStop();
|
|
}
|
|
}
|
|
|
|
async $setRemoteEnvironment(_env: { [key: string]: string | null }): Promise<void> {
|
|
return;
|
|
}
|
|
|
|
private async _waitForDebuggerAttachment(waitTimeout = 5000) {
|
|
// debugger attaches async, waiting for it fixes #106698 and #99222
|
|
if (!this._initData.environment.isExtensionDevelopmentDebug) {
|
|
return;
|
|
}
|
|
|
|
const deadline = Date.now() + waitTimeout;
|
|
while (Date.now() < deadline && !('__jsDebugIsReady' in globalThis)) {
|
|
await timeout(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
function ensureSuffix(path: string, suffix: string): string {
|
|
return path.endsWith(suffix) ? path : path + suffix;
|
|
}
|
|
|
|
// copied from bootstrap-fork.js
|
|
function wrapConsoleMethods(service: MainThreadConsoleShape, callToNative: boolean) {
|
|
wrap('info', 'log');
|
|
wrap('log', 'log');
|
|
wrap('warn', 'warn');
|
|
wrap('error', 'error');
|
|
|
|
function wrap(method: 'error' | 'warn' | 'info' | 'log', severity: 'error' | 'warn' | 'log') {
|
|
const original = console[method];
|
|
console[method] = function () {
|
|
service.$logExtensionHostMessage({ type: '__$console', severity, arguments: safeToArray(arguments) });
|
|
if (callToNative) {
|
|
original.apply(console, arguments as any);
|
|
}
|
|
};
|
|
}
|
|
|
|
const MAX_LENGTH = 100000;
|
|
|
|
function safeToArray(args: IArguments) {
|
|
const seen: any[] = [];
|
|
const argsArray = [];
|
|
|
|
// Massage some arguments with special treatment
|
|
if (args.length) {
|
|
for (let i = 0; i < args.length; i++) {
|
|
|
|
// Any argument of type 'undefined' needs to be specially treated because
|
|
// JSON.stringify will simply ignore those. We replace them with the string
|
|
// 'undefined' which is not 100% right, but good enough to be logged to console
|
|
if (typeof args[i] === 'undefined') {
|
|
args[i] = 'undefined';
|
|
}
|
|
|
|
// Any argument that is an Error will be changed to be just the error stack/message
|
|
// itself because currently cannot serialize the error over entirely.
|
|
else if (args[i] instanceof Error) {
|
|
const errorObj = args[i];
|
|
if (errorObj.stack) {
|
|
args[i] = errorObj.stack;
|
|
} else {
|
|
args[i] = errorObj.toString();
|
|
}
|
|
}
|
|
|
|
argsArray.push(args[i]);
|
|
}
|
|
}
|
|
|
|
try {
|
|
const res = JSON.stringify(argsArray, function (key, value) {
|
|
|
|
// Objects get special treatment to prevent circles
|
|
if (value && typeof value === 'object') {
|
|
if (seen.indexOf(value) !== -1) {
|
|
return '[Circular]';
|
|
}
|
|
|
|
seen.push(value);
|
|
}
|
|
|
|
return value;
|
|
});
|
|
|
|
if (res.length > MAX_LENGTH) {
|
|
return 'Output omitted for a large object that exceeds the limits';
|
|
}
|
|
|
|
return res;
|
|
} catch (error) {
|
|
return `Output omitted for an object that cannot be inspected ('${error.toString()}')`;
|
|
}
|
|
}
|
|
}
|