diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 26c0e507398..f08d88bf348 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -16,7 +16,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, IInitData, IMainContext, MainContext, MainThreadKeytarShape, IEnvironment } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, IInitData, IMainContext, MainContext, MainThreadKeytarShape, IEnvironment, MainThreadWindowShape, MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/node/extHostClipboard'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; @@ -65,6 +65,7 @@ import { originalFSPath } from 'vs/base/common/resources'; import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { withNullAsUndefined } from 'vs/base/common/types'; import { values } from 'vs/base/common/collections'; +import { endsWith } from 'vs/base/common/strings'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -871,9 +872,13 @@ class Extension implements vscode.Extension { } } +interface LoadFunction { + (request: string, parent: { filename: string; }, isMain: any): any; +} + interface INodeModuleFactory { readonly nodeModuleName: string | string[]; - load(request: string, parent: { filename: string; }): any; + load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any; alternaiveModuleName?(name: string): string | undefined; } @@ -903,7 +908,7 @@ export class NodeModuleRequireInterceptor { if (!factories.has(request)) { return original.apply(this, arguments); } - return factories.get(request)!.load(request, parent); + return factories.get(request)!.load(request, parent, isMain, original); }; } @@ -974,7 +979,6 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { private alternativeNames: Set | undefined; private _impl: IKeytarModule; - constructor(mainThreadKeytar: MainThreadKeytarShape, environment: IEnvironment) { if (environment.appRoot) { let appRoot = environment.appRoot.fsPath; @@ -1015,7 +1019,8 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { if (length <= 7 || !this.alternativeNames) { return undefined; } - if (name.match(/[\\\/]keytar$/)) { + const sep = length - 7; + if ((name.charAt(sep) === '/' || name.charAt(sep) === '\\') && endsWith(name, 'keytar')) { name = name.replace(/\\/g, '/'); if (this.alternativeNames.has(name)) { return 'keytar'; @@ -1024,3 +1029,82 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { return undefined; } } + +interface OpenOptions { + wait: boolean; + app: string | string[]; +} + +interface IOriginalOpen { + (target: string, options?: OpenOptions): Thenable; +} + +interface IOpenModule { + (target: string, options?: OpenOptions): Thenable; +} + +export class OpenNodeModuleFactory implements INodeModuleFactory { + + public readonly nodeModuleName: string[] = ['open', 'opn']; + + private _extensionId: string | undefined; + private _original: IOriginalOpen; + private _impl: IOpenModule; + + constructor(mainThreadWindow: MainThreadWindowShape, private _mainThreadTelemerty: MainThreadTelemetryShape, private readonly _extensionPaths: TernarySearchTree) { + this._impl = (target, options) => { + const uri: URI = URI.parse(target); + // If we have options use the original method. + if (options) { + return this.callOriginal(target, options); + } + if (uri.scheme === 'http' || uri.scheme === 'https') { + return mainThreadWindow.$openUri(uri); + } else if (uri.scheme === 'mailto') { + return mainThreadWindow.$openUri(uri); + } + return this.callOriginal(target, options); + }; + } + + public load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any { + // get extension id from filename and api for extension + const extension = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + if (extension) { + this._extensionId = extension.identifier.value; + this.sendShimmingTelemetry(); + } + + this._original = original(request, parent, isMain); + return this._impl; + } + + private callOriginal(target: string, options: OpenOptions | undefined): Thenable { + this.sendNoForwardTelemetry(); + return this._original(target, options); + } + + private sendShimmingTelemetry(): void { + if (!this._extensionId) { + return; + } + /* __GDPR__ + "shimming.open" : { + "extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this._mainThreadTelemerty.$publicLog('shimming.open', { extension: this._extensionId }); + } + + private sendNoForwardTelemetry(): void { + if (!this._extensionId) { + return; + } + /* __GDPR__ + "shimming.open.call.noForward" : { + "extension": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this._mainThreadTelemerty.$publicLog('shimming.open.call.noForward', { extension: this._extensionId }); + } +} diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 6fb7c51704f..374e7907f38 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -12,7 +12,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { createApiFactory, IExtensionApiFactory, NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory } from 'vs/workbench/api/node/extHost.api.impl'; +import { createApiFactory, IExtensionApiFactory, NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHost.api.impl'; import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule, HostExtension } from 'vs/workbench/api/node/extHostExtensionActivator'; @@ -248,6 +248,13 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { const extensionPaths = await this.getExtensionPathIndex(); NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider)); NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._environment)); + if (this._initData.remoteAuthority) { + NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( + this._extHostContext.getProxy(MainContext.MainThreadWindow), + this._extHostContext.getProxy(MainContext.MainThreadTelemetry), + extensionPaths + )); + } // Do this when extension service exists, but extensions are not being activated yet. await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy);