@@ -17,14 +17,36 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
1717import * as colorRegistry from 'vs/platform/theme/common/colorRegistry' ;
1818import { DARK , ITheme , IThemeService , LIGHT } from 'vs/platform/theme/common/themeService' ;
1919import { registerFileProtocol , WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols' ;
20- import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService' ;
21- import { WebviewFindWidget } from '../browser/webviewFindWidget' ;
20+ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts' ;
2221import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
2322import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions' ;
23+ import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService' ;
24+ import { WebviewFindWidget } from '../browser/webviewFindWidget' ;
2425import { WebviewContentOptions , WebviewPortMapping , WebviewOptions , Webview } from 'vs/workbench/contrib/webview/common/webview' ;
26+ import { ITunnelService , RemoteTunnel } from 'vs/platform/remote/common/tunnel' ;
2527import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
2628import { IEditorOptions , EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions' ;
2729
30+ export interface WebviewPortMapping {
31+ readonly port : number ;
32+ readonly resolvedPort : number ;
33+ }
34+
35+ export interface WebviewOptions {
36+ readonly allowSvgs ?: boolean ;
37+ readonly extension ?: {
38+ readonly location : URI ;
39+ readonly id ?: ExtensionIdentifier ;
40+ } ;
41+ readonly enableFindWidget ?: boolean ;
42+ }
43+
44+ export interface WebviewContentOptions {
45+ readonly allowScripts ?: boolean ;
46+ readonly svgWhiteList ?: string [ ] ;
47+ readonly localResourceRoots ?: ReadonlyArray < URI > ;
48+ readonly portMappings ?: ReadonlyArray < WebviewPortMapping > ;
49+ }
2850
2951interface IKeydownEvent {
3052 key : string ;
@@ -126,11 +148,15 @@ class WebviewProtocolProvider extends Disposable {
126148
127149class WebviewPortMappingProvider extends Disposable {
128150
151+ private readonly _tunnels = new Map < number , Promise < RemoteTunnel > > ( ) ;
152+
129153 constructor (
130154 session : WebviewSession ,
155+ extensionLocation : URI | undefined ,
131156 mappings : ( ) => ReadonlyArray < WebviewPortMapping > ,
157+ private readonly tunnelService : ITunnelService ,
132158 extensionId : ExtensionIdentifier | undefined ,
133- @ITelemetryService telemetryService : ITelemetryService ,
159+ @ITelemetryService telemetryService : ITelemetryService
134160 ) {
135161 super ( ) ;
136162
@@ -148,7 +174,7 @@ class WebviewPortMappingProvider extends Disposable {
148174 hasLogged = true ;
149175
150176 /* __GDPR__
151- "webview.accessLocalhost" : {
177+ "webview.accessLocalhost" : {
152178 "extension" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
153179 }
154180 */
@@ -157,19 +183,53 @@ class WebviewPortMappingProvider extends Disposable {
157183
158184 const port = + localhostMatch [ 1 ] ;
159185 for ( const mapping of mappings ( ) ) {
160- if ( mapping . port === port && mapping . port !== mapping . resolvedPort ) {
161- return {
162- redirectURL : details . url . replace (
163- new RegExp ( `^${ uri . scheme } ://localhost:${ mapping . port } /` ) ,
164- `${ uri . scheme } ://localhost:${ mapping . resolvedPort } /` )
165- } ;
186+ if ( mapping . port === port ) {
187+ if ( extensionLocation && extensionLocation . scheme === REMOTE_HOST_SCHEME ) {
188+ const tunnel = await this . getOrCreateTunnel ( mapping . resolvedPort ) ;
189+ if ( tunnel ) {
190+ return {
191+ redirectURL : details . url . replace (
192+ new RegExp ( `^${ uri . scheme } ://localhost:${ mapping . port } /` ) ,
193+ `${ uri . scheme } ://localhost:${ tunnel . tunnelLocalPort } /` )
194+ } ;
195+ }
196+ }
197+
198+ if ( mapping . port !== mapping . resolvedPort ) {
199+ return {
200+ redirectURL : details . url . replace (
201+ new RegExp ( `^${ uri . scheme } ://localhost:${ mapping . port } /` ) ,
202+ `${ uri . scheme } ://localhost:${ mapping . resolvedPort } /` )
203+ } ;
204+ }
166205 }
167206 }
168207 }
169208
170209 return undefined ;
171210 } ) ;
172211 }
212+
213+ dispose ( ) {
214+ super . dispose ( ) ;
215+
216+ for ( const tunnel of this . _tunnels . values ( ) ) {
217+ tunnel . then ( tunnel => tunnel . dispose ( ) ) ;
218+ }
219+ this . _tunnels . clear ( ) ;
220+ }
221+
222+ private getOrCreateTunnel ( remotePort : number ) : Promise < RemoteTunnel > | undefined {
223+ const existing = this . _tunnels . get ( remotePort ) ;
224+ if ( existing ) {
225+ return existing ;
226+ }
227+ const tunnel = this . tunnelService . openTunnel ( remotePort ) ;
228+ if ( tunnel ) {
229+ this . _tunnels . set ( remotePort , tunnel ) ;
230+ }
231+ return tunnel ;
232+ }
173233}
174234
175235class SvgBlocker extends Disposable {
@@ -314,6 +374,7 @@ export class WebviewElement extends Disposable implements Webview {
314374 @IThemeService themeService : IThemeService ,
315375 @IEnvironmentService environmentService : IEnvironmentService ,
316376 @IFileService fileService : IFileService ,
377+ @ITunnelService tunnelService : ITunnelService ,
317378 @ITelemetryService telemetryService : ITelemetryService ,
318379 @IConfigurationService private readonly _configurationService : IConfigurationService ,
319380 ) {
@@ -354,10 +415,12 @@ export class WebviewElement extends Disposable implements Webview {
354415
355416 this . _register ( new WebviewPortMappingProvider (
356417 session ,
357- ( ) => ( this . _contentOptions . portMappings || [ ] ) ,
418+ _options . extension ? _options . extension . location : undefined ,
419+ ( ) => ( this . _contentOptions . portMappings || [ { port : 3000 , resolvedPort : 4000 } ] ) ,
420+ tunnelService ,
358421 _options . extension ? _options . extension . id : undefined ,
359- telemetryService ) ) ;
360-
422+ telemetryService
423+ ) ) ;
361424
362425 if ( ! this . _options . allowSvgs ) {
363426 const svgBlocker = this . _register ( new SvgBlocker ( session , this . _contentOptions ) ) ;
0 commit comments