未验证 提交 f7ec17d2 编写于 作者: M Matt Bierner 提交者: GitHub

Move the webview port mapping from renderer to main process (#100342)

* Move the webview port mapping from renderer to main process

Fixes #95955

Our port mapping impl for webview currently relies on `getWebContents` to handle port mapping in the renderer process. This API has been deprecated by electron.

This change instead moves the webview port mapping handler to the main process. To make this change, I also realized that the tunnel service needed to be moved from `vs/workbench` to `vs/platform` so that we could consume it from the main process

Other changes made as part of this refactoring:

-  Register all webview in a `webview` partition. This ensures that our port mapping manger only intercepts requests made inside webviews.

- Send the `webContentsId` to the main process. This is required to implement `onBeforeRequest`. Unfortunatly the referrer always seems to be undefined for these requests

- Have the tunnel service take a resolved authority instead of taking the raw authority. This was required due to the tunnel service moving to `vs/platform`

* Cleanup and adding url filter
上级 d7ae69e9
......@@ -47,6 +47,9 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
import { IProductService } from 'vs/platform/product/common/productService';
class ExpectedError extends Error {
readonly isExpected = true;
......@@ -162,6 +165,8 @@ class CodeMain {
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
services.set(ISignService, new SyncDescriptor(SignService));
services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService));
services.set(IProductService, { _serviceBrand: undefined, ...product });
services.set(ITunnelService, new SyncDescriptor(TunnelService));
return [new InstantiationService(services, true), instanceEnvironment, environmentService];
}
......
......@@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection';
export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
......@@ -35,7 +37,7 @@ export interface ITunnelService {
readonly onTunnelOpened: Event<RemoteTunnel>;
readonly onTunnelClosed: Event<{ host: string, port: number }>;
openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
openTunnel(resolveAuthority: IAddress | undefined, remoteHost: string | undefined, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
}
......@@ -53,3 +55,144 @@ export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address:
port: +localhostMatch[2],
};
}
export abstract class AbstractTunnelService implements ITunnelService {
declare readonly _serviceBrand: undefined;
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
protected _tunnelProvider: ITunnelProvider | undefined;
public constructor(
@ILogService protected readonly logService: ILogService
) { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
if (!provider) {
return {
dispose: () => { }
};
}
this._tunnelProvider = provider;
return {
dispose: () => {
this._tunnelProvider = undefined;
}
};
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
}
dispose(): void {
for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose());
}
portMap.clear();
}
this._tunnels.clear();
}
openTunnel(resolvedAuthority: IAddress | undefined, remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
if (!resolvedAuthority) {
return undefined;
}
if (!remoteHost || (remoteHost === '127.0.0.1')) {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(resolvedAuthority, remoteHost, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => {
const newTunnel = this.makeTunnel(tunnel);
if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) {
this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.');
}
this._onTunnelOpened.fire(newTunnel);
return newTunnel;
});
}
private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel {
return {
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
}
}
};
}
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose(true);
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
});
if (this._tunnels.has(remoteHost)) {
this._tunnels.get(remoteHost)!.delete(remotePort);
}
return disposePromise;
}
}
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
const portMap = this._tunnels.get(remoteHost);
if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0;
await this.tryDisposeTunnel(remoteHost, remotePort, value);
}
}
protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
if (!this._tunnels.has(remoteHost)) {
this._tunnels.set(remoteHost, new Map());
}
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
}
protected abstract retainOrCreateTunnel(resolveRemoteAuthority: IAddress, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
}
export class TunnelService extends AbstractTunnelService {
protected retainOrCreateTunnel(_resolveRemoteAuthority: IAddress, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
++existing.refcount;
return existing.value;
}
if (this._tunnelProvider) {
const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } });
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
}
return undefined;
}
}
......@@ -6,18 +6,14 @@
import * as net from 'net';
import { Barrier } from 'vs/base/common/async';
import { Disposable } from 'vs/base/common/lifecycle';
import { findFreePortFaster } from 'vs/base/node/ports';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { connectRemoteAgentTunnel, IAddress, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { findFreePortFaster } from 'vs/base/node/ports';
import { AbstractTunnelService } from 'vs/workbench/services/remote/common/tunnelService';
async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
......@@ -128,16 +124,14 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
export class TunnelService extends AbstractTunnelService {
public constructor(
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ILogService logService: ILogService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ISignService private readonly signService: ISignService,
@IProductService private readonly productService: IProductService
) {
super(environmentService, logService);
super(logService);
}
protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
protected retainOrCreateTunnel(resolveRemoteAuthority: IAddress, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
......@@ -157,8 +151,7 @@ export class TunnelService extends AbstractTunnelService {
socketFactory: nodeSocketFactory,
addressProvider: {
getAddress: async () => {
const { authority } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
return { host: authority.host, port: authority.port };
return resolveRemoteAuthority;
}
},
signService: this.signService,
......@@ -171,5 +164,3 @@ export class TunnelService extends AbstractTunnelService {
}
}
}
registerSingleton(ITunnelService, TunnelService, true);
......@@ -15,6 +15,10 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IRequestService } from 'vs/platform/request/common/request';
import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes';
export const webviewPartitionId = 'webview';
export namespace WebviewResourceResponse {
export enum Type { Success, Failed, AccessDenied }
......
......@@ -6,13 +6,14 @@
import { UriComponents } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IWebviewPortMapping } from 'vs/platform/webview/common/webviewPortMapping';
export const IWebviewManagerService = createDecorator<IWebviewManagerService>('webviewManagerService');
export interface IWebviewManagerService {
_serviceBrand: unknown;
registerWebview(id: string, metadata: RegisterWebviewMetadata): Promise<void>;
registerWebview(id: string, webContentsId: number, metadata: RegisterWebviewMetadata): Promise<void>;
unregisterWebview(id: string): Promise<void>;
updateWebviewMetadata(id: string, metadataDelta: Partial<RegisterWebviewMetadata>): Promise<void>;
......@@ -23,4 +24,5 @@ export interface RegisterWebviewMetadata {
readonly extensionLocation: UriComponents | undefined;
readonly localResourceRoots: readonly UriComponents[];
readonly remoteConnectionData: IRemoteConnectionData | null;
readonly portMappings: readonly IWebviewPortMapping[];
}
......@@ -3,36 +3,42 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITunnelService, RemoteTunnel, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel';
import { extractLocalHostUriMetaDataForPortMapping, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
export class WebviewPortMappingManager extends Disposable {
export interface IWebviewPortMapping {
webviewPort: number;
extensionHostPort: number;
}
/**
* Manages port mappings for a single webview.
*/
export class WebviewPortMappingManager implements IDisposable {
private readonly _tunnels = new Map<number, Promise<RemoteTunnel>>();
constructor(
private readonly getExtensionLocation: () => URI | undefined,
private readonly mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
private readonly _getExtensionLocation: () => URI | undefined,
private readonly _getMappings: () => readonly IWebviewPortMapping[],
private readonly tunnelService: ITunnelService
) {
super();
}
) { }
public async getRedirect(url: string): Promise<string | undefined> {
public async getRedirect(resolveAuthority: IAddress, url: string): Promise<string | undefined> {
const uri = URI.parse(url);
const requestLocalHostInfo = extractLocalHostUriMetaDataForPortMapping(uri);
if (!requestLocalHostInfo) {
return undefined;
}
for (const mapping of this.mappings()) {
for (const mapping of this._getMappings()) {
if (mapping.webviewPort === requestLocalHostInfo.port) {
const extensionLocation = this.getExtensionLocation();
const extensionLocation = this._getExtensionLocation();
if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) {
const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort);
const tunnel = await this.getOrCreateTunnel(resolveAuthority, mapping.extensionHostPort);
if (tunnel) {
if (tunnel.tunnelLocalPort === mapping.webviewPort) {
return undefined;
......@@ -55,20 +61,18 @@ export class WebviewPortMappingManager extends Disposable {
}
dispose() {
super.dispose();
for (const tunnel of this._tunnels.values()) {
tunnel.then(tunnel => tunnel.dispose());
}
this._tunnels.clear();
}
private getOrCreateTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
private getOrCreateTunnel(remoteAuthority: IAddress, remotePort: number): Promise<RemoteTunnel> | undefined {
const existing = this._tunnels.get(remotePort);
if (existing) {
return existing;
}
const tunnel = this.tunnelService.openTunnel(undefined, remotePort);
const tunnel = this.tunnelService.openTunnel(remoteAuthority, undefined, remotePort);
if (tunnel) {
this._tunnels.set(remotePort, tunnel);
}
......
......@@ -4,42 +4,65 @@
*--------------------------------------------------------------------------------------------*/
import { webContents } from 'electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { IRequestService } from 'vs/platform/request/common/request';
import { IWebviewManagerService, RegisterWebviewMetadata } from 'vs/platform/webview/common/webviewManagerService';
import { WebviewPortMappingProvider } from 'vs/platform/webview/electron-main/webviewPortMappingProvider';
import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider';
export class WebviewMainService implements IWebviewManagerService {
export class WebviewMainService extends Disposable implements IWebviewManagerService {
declare readonly _serviceBrand: undefined;
private protocolProvider: WebviewProtocolProvider;
private readonly protocolProvider: WebviewProtocolProvider;
private readonly portMappingProvider: WebviewPortMappingProvider;
constructor(
@IFileService fileService: IFileService,
@IRequestService requestService: IRequestService,
@ITunnelService tunnelService: ITunnelService,
) {
this.protocolProvider = new WebviewProtocolProvider(fileService, requestService);
super();
this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, requestService));
this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService));
}
public async registerWebview(id: string, metadata: RegisterWebviewMetadata): Promise<void> {
public async registerWebview(id: string, webContentsId: number, metadata: RegisterWebviewMetadata): Promise<void> {
const extensionLocation = metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined;
this.protocolProvider.registerWebview(id, {
...metadata,
extensionLocation: metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined,
extensionLocation,
localResourceRoots: metadata.localResourceRoots.map(x => URI.from(x))
});
this.portMappingProvider.registerWebview(id, webContentsId, {
extensionLocation,
mappings: metadata.portMappings,
resolvedAuthority: metadata.remoteConnectionData,
});
}
public async unregisterWebview(id: string): Promise<void> {
this.protocolProvider.unreigsterWebview(id);
this.protocolProvider.unregisterWebview(id);
this.portMappingProvider.unregisterWebview(id);
}
public async updateWebviewMetadata(id: string, metadataDelta: Partial<RegisterWebviewMetadata>): Promise<void> {
public async updateWebviewMetadata(id: string, metaDataDelta: Partial<RegisterWebviewMetadata>): Promise<void> {
const extensionLocation = metaDataDelta.extensionLocation ? URI.from(metaDataDelta.extensionLocation) : undefined;
this.protocolProvider.updateWebviewMetadata(id, {
...metadataDelta,
localResourceRoots: metadataDelta.localResourceRoots?.map(x => URI.from(x)),
extensionLocation: metadataDelta.extensionLocation ? URI.from(metadataDelta.extensionLocation) : undefined,
...metaDataDelta,
extensionLocation,
localResourceRoots: metaDataDelta.localResourceRoots?.map(x => URI.from(x)),
});
this.portMappingProvider.updateWebviewMetadata(id, {
...metaDataDelta,
extensionLocation,
});
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { session } from 'electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader';
import { IWebviewPortMapping, WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping';
interface PortMappingData {
readonly extensionLocation: URI | undefined;
readonly mappings: readonly IWebviewPortMapping[];
readonly resolvedAuthority: IAddress | null | undefined;
}
export class WebviewPortMappingProvider extends Disposable {
private readonly _webviewData = new Map<string, {
readonly webContentsId: number;
readonly manager: WebviewPortMappingManager;
metadata: PortMappingData;
}>();
private _webContentsIdsToWebviewIds = new Map<number, /* id */ string>();
constructor(
@ITunnelService private readonly _tunnelService: ITunnelService,
) {
super();
const sess = session.fromPartition(webviewPartitionId);
sess.webRequest.onBeforeRequest({
urls: [
'*://localhost:*/',
'*://127.0.0.1:*/',
'*://0.0.0.0:*/',
]
}, async (details, callback) => {
const webviewId = details.webContentsId && this._webContentsIdsToWebviewIds.get(details.webContentsId);
if (!webviewId) {
return callback({});
}
const entry = this._webviewData.get(webviewId);
if (!entry || !entry.metadata.resolvedAuthority) {
return callback({});
}
const redirect = await entry.manager.getRedirect(entry.metadata.resolvedAuthority, details.url);
return callback(redirect ? { redirectURL: redirect } : {});
});
}
public async registerWebview(id: string, webContentsId: number, metadata: PortMappingData): Promise<void> {
const manager = new WebviewPortMappingManager(
() => this._webviewData.get(id)?.metadata.extensionLocation,
() => this._webviewData.get(id)?.metadata.mappings || [],
this._tunnelService);
this._webviewData.set(id, { webContentsId, metadata, manager });
this._webContentsIdsToWebviewIds.set(webContentsId, id);
}
public unregisterWebview(id: string): void {
const existing = this._webviewData.get(id);
if (existing) {
existing.manager.dispose();
this._webviewData.delete(id);
this._webContentsIdsToWebviewIds.delete(existing.webContentsId);
}
}
public async updateWebviewMetadata(id: string, metadataDelta: Partial<PortMappingData>): Promise<void> {
const entry = this._webviewData.get(id);
if (entry) {
this._webviewData.set(id, {
...entry,
...metadataDelta,
});
}
}
}
......@@ -3,16 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { protocol } from 'electron';
import { session } from 'electron';
import { Readable } from 'stream';
import { VSBufferReadableStream } from 'vs/base/common/buffer';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRequestService } from 'vs/platform/request/common/request';
import { loadLocalResourceStream, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
import { VSBufferReadableStream } from 'vs/base/common/buffer';
import { Readable } from 'stream';
import { loadLocalResourceStream, webviewPartitionId, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
interface WebviewMetadata {
readonly extensionLocation: URI | undefined;
......@@ -30,7 +30,9 @@ export class WebviewProtocolProvider extends Disposable {
) {
super();
protocol.registerStreamProtocol(Schemas.vscodeWebviewResource, async (request, callback): Promise<void> => {
const sess = session.fromPartition(webviewPartitionId);
sess.protocol.registerStreamProtocol(Schemas.vscodeWebviewResource, async (request, callback): Promise<void> => {
try {
const uri = URI.parse(request.url);
......@@ -65,7 +67,7 @@ export class WebviewProtocolProvider extends Disposable {
return callback({ data: null, statusCode: 404 });
});
this._register(toDisposable(() => protocol.unregisterProtocol(Schemas.vscodeWebviewResource)));
this._register(toDisposable(() => sess.protocol.unregisterProtocol(Schemas.vscodeWebviewResource)));
}
private streamToNodeReadable(stream: VSBufferReadableStream): Readable {
......@@ -116,7 +118,7 @@ export class WebviewProtocolProvider extends Disposable {
this.webviewMetadata.set(id, metadata);
}
public unreigsterWebview(id: string): void {
public unregisterWebview(id: string): void {
this.webviewMetadata.delete(id);
}
......
......@@ -5,6 +5,7 @@
import { addDisposableListener } from 'vs/base/browser/dom';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isWeb } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -12,12 +13,12 @@ import { IFileService } from 'vs/platform/files/common/files';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { loadLocalResource, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
import { WebviewPortMappingManager } from 'vs/platform/webview/common/webviewPortMapping';
import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isWeb } from 'vs/base/common/platform';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Webview {
private readonly _portMappingManager: WebviewPortMappingManager;
......@@ -32,17 +33,18 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
@IFileService private readonly fileService: IFileService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITelemetryService telemetryService: ITelemetryService,
@IEnvironmentService environementService: IEnvironmentService,
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IEnvironmentService environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService private readonly _workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super(id, options, contentOptions, extension, webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService);
super(id, options, contentOptions, extension, webviewThemeDataProvider, telemetryService, environmentService, _workbenchEnvironmentService);
if (!this.useExternalEndpoint && (!workbenchEnvironmentService.options || typeof workbenchEnvironmentService.webviewExternalEndpoint !== 'string')) {
if (!this.useExternalEndpoint && (!_workbenchEnvironmentService.options || typeof _workbenchEnvironmentService.webviewExternalEndpoint !== 'string')) {
throw new Error('To use iframe based webviews, you must configure `environmentService.webviewExternalEndpoint`');
}
this._portMappingManager = this._register(new WebviewPortMappingManager(
() => this.extension ? this.extension.location : undefined,
() => this.extension?.location,
() => this.content.options.portMapping || [],
tunnelService
));
......@@ -156,7 +158,9 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
}
private async localLocalhost(origin: string) {
const redirect = await this._portMappingManager.getRedirect(origin);
const authority = this._workbenchEnvironmentService.configuration.remoteAuthority;
const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined;
const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined;
return this._send('did-load-localhost', {
origin,
location: redirect
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron';
import { FindInPageOptions, WebviewTag } from 'electron';
import { addDisposableListener } from 'vs/base/browser/dom';
import { equals } from 'vs/base/common/arrays';
import { ThrottledDelayer } from 'vs/base/common/async';
......@@ -20,125 +20,54 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { webviewPartitionId } from 'vs/platform/webview/common/resourceLoader';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget';
class WebviewTagHandle extends Disposable {
class WebviewResourceRequestManager extends Disposable {
private _webContents: undefined | WebContents | 'destroyed';
private readonly _webviewManagerService: IWebviewManagerService;
public constructor(
public readonly webview: WebviewTag,
) {
super();
this._register(addDisposableListener(this.webview, 'destroyed', () => {
this._webContents = 'destroyed';
}));
this._register(addDisposableListener(this.webview, 'did-start-loading', once(() => {
const contents = this.webContents;
if (contents) {
this._onFirstLoad.fire(contents);
this._register(toDisposable(() => {
contents.removeAllListeners();
}));
}
})));
}
private readonly _onFirstLoad = this._register(new Emitter<WebContents>());
public readonly onFirstLoad = this._onFirstLoad.event;
public get webContents(): WebContents | undefined {
if (this._webContents === 'destroyed') {
return undefined;
}
if (this._webContents) {
return this._webContents;
}
this._webContents = this.webview.getWebContents();
return this._webContents;
}
}
type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise<Response | undefined>;
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined;
class WebviewSession extends Disposable {
private readonly _onBeforeRequestDelegates: Array<OnBeforeRequestDelegate> = [];
private readonly _onHeadersReceivedDelegates: Array<OnHeadersReceivedDelegate> = [];
public constructor(
webviewHandle: WebviewTagHandle,
) {
super();
this._register(webviewHandle.onFirstLoad(contents => {
contents.session.webRequest.onBeforeRequest(async (details, callback) => {
for (const delegate of this._onBeforeRequestDelegates) {
const result = await delegate(details);
if (typeof result !== 'undefined') {
callback(result);
return;
}
}
callback({});
});
contents.session.webRequest.onHeadersReceived((details, callback) => {
for (const delegate of this._onHeadersReceivedDelegates) {
const result = delegate(details);
if (typeof result !== 'undefined') {
callback(result);
return;
}
}
callback({ cancel: false });
});
}));
}
public onBeforeRequest(delegate: OnBeforeRequestDelegate) {
this._onBeforeRequestDelegates.push(delegate);
}
public onHeadersReceived(delegate: OnHeadersReceivedDelegate) {
this._onHeadersReceivedDelegates.push(delegate);
}
}
class WebviewProtocolProvider extends Disposable {
private _localResourceRoots: ReadonlyArray<URI>;
private _portMappings: ReadonlyArray<modes.IWebviewPortMapping>;
private _ready?: Promise<void>;
private _localResourceRoots: ReadonlyArray<URI>;
constructor(
private readonly id: string,
private readonly extension: WebviewExtensionDescription | undefined,
initialLocalResourceRoots: ReadonlyArray<URI>,
private readonly _webviewManagerService: IWebviewManagerService,
remoteAuthorityResolverService: IRemoteAuthorityResolverService,
environmentService: IWorkbenchEnvironmentService,
webview: WebviewTag,
initialContentOptions: WebviewContentOptions,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IMainProcessService mainProcessService: IMainProcessService,
) {
super();
this._localResourceRoots = initialLocalResourceRoots;
this._webviewManagerService = createChannelSender<IWebviewManagerService>(mainProcessService.getChannel('webview'));
this._localResourceRoots = initialContentOptions.localResourceRoots || [];
this._portMappings = initialContentOptions.portMapping || [];
const remoteAuthority = environmentService.configuration.remoteAuthority;
this._ready = _webviewManagerService.registerWebview(this.id, {
extensionLocation: this.extension?.location.toJSON(),
localResourceRoots: initialLocalResourceRoots.map(x => x.toJSON()),
remoteConnectionData: remoteAuthority ? remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null,
const remoteConnectionData = remoteAuthority ? remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null;
this._ready = new Promise(resolve => {
this._register(addDisposableListener(webview!, 'did-start-loading', once(() => {
const webContentsId = webview.getWebContentsId();
this._webviewManagerService.registerWebview(this.id, webContentsId, {
extensionLocation: this.extension?.location.toJSON(),
localResourceRoots: this._localResourceRoots.map(x => x.toJSON()),
remoteConnectionData: remoteConnectionData,
portMappings: this._portMappings,
}).finally(() => resolve());
})));
});
if (remoteAuthority) {
......@@ -153,16 +82,25 @@ class WebviewProtocolProvider extends Disposable {
this._register(toDisposable(() => this._webviewManagerService.unregisterWebview(this.id)));
}
public update(localResourceRoots: ReadonlyArray<URI>) {
if (equals(this._localResourceRoots, localResourceRoots, (a, b) => a.toString() === b.toString())) {
public update(options: WebviewContentOptions) {
const localResourceRoots = options.localResourceRoots || [];
const portMappings = options.portMapping || [];
if (
equals(this._localResourceRoots, localResourceRoots, (a, b) => a.toString() === b.toString())
&& equals(this._portMappings, portMappings, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort)
) {
return;
}
this._localResourceRoots = localResourceRoots;
this._portMappings = portMappings;
const update = this._webviewManagerService.updateWebviewMetadata(this.id, {
localResourceRoots: localResourceRoots.map(x => x.toJSON()),
portMappings: portMappings,
});
this._ready = this._ready?.then(() => update);
}
......@@ -171,24 +109,6 @@ class WebviewProtocolProvider extends Disposable {
}
}
class WebviewPortMappingProvider extends Disposable {
constructor(
session: WebviewSession,
getExtensionLocation: () => URI | undefined,
mappings: () => ReadonlyArray<modes.IWebviewPortMapping>,
tunnelService: ITunnelService,
) {
super();
const manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService));
session.onBeforeRequest(async details => {
const redirect = await manager.getRedirect(details.url);
return redirect ? { redirectURL: redirect } : undefined;
});
}
}
class WebviewKeyboardHandler {
private readonly _webviews = new Set<WebviewTag>();
......@@ -266,7 +186,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
private _webviewFindWidget: WebviewFindWidget | undefined;
private _findStarted: boolean = false;
private readonly _protocolProvider: WebviewProtocolProvider;
private readonly _resourceRequestManager: WebviewResourceRequestManager;
private readonly _focusDelayer = this._register(new ThrottledDelayer(10));
private _elementFocusImpl!: (options?: FocusOptions | undefined) => void;
......@@ -280,29 +200,15 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
extension: WebviewExtensionDescription | undefined,
private readonly _webviewThemeDataProvider: WebviewThemeDataProvider,
@IInstantiationService instantiationService: IInstantiationService,
@ITunnelService tunnelService: ITunnelService,
@ITelemetryService telemetryService: ITelemetryService,
@IEnvironmentService environementService: IEnvironmentService,
@IEnvironmentService environmentService: IEnvironmentService,
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IConfigurationService configurationService: IConfigurationService,
@IMainProcessService mainProcessService: IMainProcessService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super(id, options, contentOptions, extension, _webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService);
const webviewManagerService = createChannelSender<IWebviewManagerService>(mainProcessService.getChannel('webview'));
super(id, options, contentOptions, extension, _webviewThemeDataProvider, telemetryService, environmentService, workbenchEnvironmentService);
const webviewAndContents = this._register(new WebviewTagHandle(this.element!));
const session = this._register(new WebviewSession(webviewAndContents));
this._protocolProvider = this._register(new WebviewProtocolProvider(id, extension, this.content.options.localResourceRoots || [], webviewManagerService, remoteAuthorityResolverService, workbenchEnvironmentService));
this._register(new WebviewPortMappingProvider(
session,
() => this.extension ? this.extension.location : undefined,
() => (this.content.options.portMapping || []),
tunnelService,
));
this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.element!, this.content.options));
this._register(addDisposableListener(this.element!, 'did-start-loading', once(() => {
this._register(ElectronWebviewBasedWebview.getWebviewKeyboardHandler(configurationService, mainProcessService).add(this.element!));
......@@ -370,6 +276,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
element.focus = () => {
this.doFocus();
};
element.setAttribute('partition', webviewPartitionId);
element.setAttribute('webpreferences', 'contextIsolation=yes');
element.className = `webview ${options.customClasses || ''}`;
......@@ -385,12 +292,15 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
}
public set contentOptions(options: WebviewContentOptions) {
this._protocolProvider.update(options.localResourceRoots || []);
this._resourceRequestManager.update(options);
super.contentOptions = options;
}
public set localResourcesRoot(resources: URI[]) {
this._protocolProvider.update(resources || []);
this._resourceRequestManager.update({
...this.contentOptions,
localResourceRoots: resources,
});
super.localResourcesRoot = resources;
}
......@@ -428,7 +338,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
protected async doPostMessage(channel: string, data?: any): Promise<void> {
this._messagePromise = this._messagePromise
.then(() => this._protocolProvider.synchronize())
.then(() => this._resourceRequestManager.synchronize())
.then(() => this.element?.send(channel, data));
}
......
......@@ -64,6 +64,7 @@ import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/
import { Event } from 'vs/base/common/event';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
export class NativeWindow extends Disposable {
......@@ -107,7 +108,8 @@ export class NativeWindow extends Disposable {
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IStorageService private readonly storageService: IStorageService,
@IProductService private readonly productService: IProductService
@IProductService private readonly productService: IProductService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super();
......@@ -471,7 +473,8 @@ export class NativeWindow extends Disposable {
if (options?.allowTunneling) {
const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri);
if (portMappingRequest) {
const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port);
const resolvedRemote = this.environmentService.configuration.remoteAuthority ? await this.remoteAuthorityResolverService.resolveAuthority(this.environmentService.configuration.remoteAuthority) : undefined;
const tunnel = await this.tunnelService.openTunnel(resolvedRemote?.authority, undefined, portMappingRequest.port);
if (tunnel) {
return {
resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }),
......
......@@ -11,7 +11,8 @@ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IEditableData } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TunnelInformation, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { TunnelInformation, TunnelDescription, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
......@@ -75,7 +76,9 @@ export class TunnelModel extends Disposable {
constructor(
@ITunnelService private readonly tunnelService: ITunnelService,
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
super();
this.forwarded = new Map();
......@@ -137,7 +140,13 @@ export class TunnelModel extends Disposable {
async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise<RemoteTunnel | void> {
const key = MakeAddress(remote.host, remote.port);
if (!this.forwarded.has(key)) {
const tunnel = await this.tunnelService.openTunnel(remote.host, remote.port, local);
const authority = this.environmentService.configuration.remoteAuthority;
const resolvedRemote = authority ? await this.remoteAuthorityResolverService.resolveAuthority(authority) : undefined;
if (!resolvedRemote) {
return;
}
const tunnel = await this.tunnelService.openTunnel(resolvedRemote.authority, remote.host, remote.port, local);
if (tunnel && tunnel.localAddress) {
const newForward: Tunnel = {
remoteHost: tunnel.tunnelRemoteHost,
......@@ -253,9 +262,11 @@ class RemoteExplorerService implements IRemoteExplorerService {
constructor(
@IStorageService private readonly storageService: IStorageService,
@ITunnelService tunnelService: ITunnelService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
) {
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService);
this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService, environmentService, remoteAuthorityResolverService);
}
set targetType(name: string[]) {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
export abstract class AbstractTunnelService implements ITunnelService {
declare readonly _serviceBrand: undefined;
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = this._onTunnelOpened.event;
private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter();
public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event;
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
protected _tunnelProvider: ITunnelProvider | undefined;
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILogService protected readonly logService: ILogService
) { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
if (!provider) {
return {
dispose: () => { }
};
}
this._tunnelProvider = provider;
return {
dispose: () => {
this._tunnelProvider = undefined;
}
};
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
}
dispose(): void {
for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose());
}
portMap.clear();
}
this._tunnels.clear();
}
openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
if (!remoteHost || (remoteHost === '127.0.0.1')) {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => {
const newTunnel = this.makeTunnel(tunnel);
if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) {
this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.');
}
this._onTunnelOpened.fire(newTunnel);
return newTunnel;
});
}
private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel {
return {
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
}
}
};
}
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose(true);
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
});
if (this._tunnels.has(remoteHost)) {
this._tunnels.get(remoteHost)!.delete(remotePort);
}
return disposePromise;
}
}
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
const portMap = this._tunnels.get(remoteHost);
if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0;
await this.tryDisposeTunnel(remoteHost, remotePort, value);
}
}
protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
if (!this._tunnels.has(remoteHost)) {
this._tunnels.set(remoteHost, new Map());
}
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
}
protected abstract retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
}
export class TunnelService extends AbstractTunnelService {
protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
++existing.refcount;
return existing.value;
}
if (this._tunnelProvider) {
const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } });
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
}
return undefined;
}
}
......@@ -52,7 +52,6 @@ import 'vs/workbench/services/telemetry/electron-browser/telemetryService';
import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService';
import 'vs/workbench/services/extensionManagement/node/extensionManagementService';
import 'vs/workbench/services/accessibility/electron-browser/accessibilityService';
import 'vs/workbench/services/remote/node/tunnelService';
import 'vs/workbench/services/backup/node/backupFileService';
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncMachinesService';
......@@ -68,9 +67,12 @@ import { ICredentialsService } from 'vs/platform/credentials/common/credentials'
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataAutoSyncService } from 'vs/workbench/contrib/userDataSync/electron-browser/userDataAutoSyncService';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
registerSingleton(ICredentialsService, KeytarCredentialsService, true);
registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService);
registerSingleton(ITunnelService, TunnelService);
//#endregion
......
......@@ -61,8 +61,7 @@ import { BackupFileService } from 'vs/workbench/services/backup/common/backupFil
import { IExtensionManagementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService';
import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService';
import { ITunnelService, TunnelService } from 'vs/platform/remote/common/tunnel';
import { ILoggerService } from 'vs/platform/log/common/log';
import { FileLoggerService } from 'vs/platform/log/common/fileLogService';
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册