Skip to content

Commit 07ee925

Browse files
author
Benjamin Pasero
committed
Web: implement browser extension debug service properly (fixes microsoft#81493)
1 parent 715cdf7 commit 07ee925

5 files changed

Lines changed: 196 additions & 57 deletions

File tree

src/vs/code/browser/workbench/workbench.ts

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -202,27 +202,41 @@ class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvi
202202

203203
class WorkspaceProvider implements IWorkspaceProvider {
204204

205-
constructor(public readonly workspace: IWorkspace) { }
206-
207-
async open(workspace: IWorkspace, options?: { reuse?: boolean }): Promise<void> {
208-
if (options && options.reuse && this.isSame(this.workspace, workspace)) {
209-
return; // return early if workspace is not changing and we are reusing window
205+
static QUERY_PARAM_EMPTY_WINDOW = 'ew';
206+
static QUERY_PARAM_FOLDER = 'folder';
207+
static QUERY_PARAM_WORKSPACE = 'workspace';
208+
209+
constructor(
210+
public readonly workspace: IWorkspace,
211+
public readonly environment: ReadonlyMap<string, string>
212+
) { }
213+
214+
async open(workspace: IWorkspace, options?: { reuse?: boolean, environment?: Map<string, string> }): Promise<void> {
215+
if (options && options.reuse && !options.environment && this.isSame(this.workspace, workspace)) {
216+
return; // return early if workspace and environment is not changing and we are reusing window
210217
}
211218

212219
// Empty
213220
let targetHref: string | undefined = undefined;
214221
if (!workspace) {
215-
targetHref = `${document.location.origin}${document.location.pathname}?ew=true`;
222+
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW}=true`;
216223
}
217224

218225
// Folder
219226
else if (isFolderToOpen(workspace)) {
220-
targetHref = `${document.location.origin}${document.location.pathname}?folder=${workspace.folderUri.path}`;
227+
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${workspace.folderUri.path}`;
221228
}
222229

223230
// Workspace
224231
else if (isWorkspaceToOpen(workspace)) {
225-
targetHref = `${document.location.origin}${document.location.pathname}?workspace=${workspace.workspaceUri.path}`;
232+
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${workspace.workspaceUri.path}`;
233+
}
234+
235+
// Environment
236+
if (options && options.environment) {
237+
for (const [key, value] of options.environment) {
238+
targetHref += `&${key}=${encodeURIComponent(value)}`;
239+
}
226240
}
227241

228242
if (targetHref) {
@@ -255,21 +269,53 @@ class WorkspaceProvider implements IWorkspaceProvider {
255269
}
256270
}
257271

258-
const configElement = document.getElementById('vscode-workbench-web-configuration');
259-
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
260-
if (!configElement || !configElementAttribute) {
261-
throw new Error('Missing web configuration element');
262-
}
272+
(function () {
263273

264-
const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
265-
options.workspaceProvider = new WorkspaceProvider(options.folderUri ? { folderUri: URI.revive(options.folderUri) } : options.workspaceUri ? { workspaceUri: URI.revive(options.workspaceUri) } : undefined);
266-
options.urlCallbackProvider = new PollingURLCallbackProvider();
267-
options.credentialsProvider = new LocalStorageCredentialsProvider();
274+
// Find config element in DOM
275+
const configElement = document.getElementById('vscode-workbench-web-configuration');
276+
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
277+
if (!configElement || !configElementAttribute) {
278+
throw new Error('Missing web configuration element');
279+
}
268280

269-
if (Array.isArray(options.staticExtensions)) {
270-
options.staticExtensions.forEach(extension => {
271-
extension.extensionLocation = URI.revive(extension.extensionLocation);
272-
});
273-
}
281+
const options: IWorkbenchConstructionOptions & { folderUri?: UriComponents, workspaceUri?: UriComponents } = JSON.parse(configElementAttribute);
282+
283+
// Determine workspace to open
284+
let workspace: IWorkspace;
285+
if (options.folderUri) {
286+
workspace = { folderUri: URI.revive(options.folderUri) };
287+
} else if (options.workspaceUri) {
288+
workspace = { workspaceUri: URI.revive(options.workspaceUri) };
289+
} else {
290+
workspace = undefined;
291+
}
292+
293+
// Find environmental properties
294+
const environment = new Map<string, string>();
295+
if (document && document.location && document.location.search) {
296+
const query = document.location.search.substring(1);
297+
const vars = query.split('&');
298+
for (let p of vars) {
299+
const pair = p.split('=');
300+
if (pair.length === 2) {
301+
const [key, value] = pair;
302+
if (key !== WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW && key !== WorkspaceProvider.QUERY_PARAM_FOLDER && key !== WorkspaceProvider.QUERY_PARAM_WORKSPACE) {
303+
environment.set(key, decodeURIComponent(value));
304+
}
305+
}
306+
}
307+
}
308+
309+
options.workspaceProvider = new WorkspaceProvider(workspace, environment);
310+
options.urlCallbackProvider = new PollingURLCallbackProvider();
311+
options.credentialsProvider = new LocalStorageCredentialsProvider();
312+
313+
if (Array.isArray(options.staticExtensions)) {
314+
options.staticExtensions.forEach(extension => {
315+
extension.extensionLocation = URI.revive(extension.extensionLocation);
316+
});
317+
}
274318

275-
create(document.body, options);
319+
// Finally create workbench
320+
create(document.body, options);
321+
})();

src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@
55

66
import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc';
77
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
8-
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
98
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
109
import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug';
1110
import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug';
1211
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1312
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
1413
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
1514
import { Event } from 'vs/base/common/event';
16-
import { IProcessEnvironment } from 'vs/base/common/platform';
1715
import { URI } from 'vs/base/common/uri';
16+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
17+
import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService';
1818

1919
class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService {
2020

21+
private workspaceProvider: IWorkspaceProvider;
22+
2123
constructor(
2224
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
23-
@IEnvironmentService environmentService: IEnvironmentService
25+
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
2426
) {
2527
const connection = remoteAgentService.getConnection();
26-
2728
let channel: IChannel;
2829
if (connection) {
2930
channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName);
@@ -35,19 +36,72 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
3536

3637
super(channel);
3738

39+
if (environmentService.options && environmentService.options.workspaceProvider) {
40+
this.workspaceProvider = environmentService.options.workspaceProvider;
41+
} else {
42+
this.workspaceProvider = { open: async () => undefined, workspace: undefined };
43+
console.warn('Extension Host Debugging not available due to missing workspace provider.');
44+
}
45+
46+
this.registerListeners(environmentService);
47+
}
48+
49+
private registerListeners(environmentService: IWorkbenchEnvironmentService): void {
50+
51+
// Reload window on reload request
3852
this._register(this.onReload(event => {
3953
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
4054
window.location.reload();
4155
}
4256
}));
57+
58+
// Close window on close request
4359
this._register(this.onClose(event => {
4460
if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) {
4561
window.close();
4662
}
4763
}));
4864
}
4965

50-
openExtensionDevelopmentHostWindow(args: string[], env: IProcessEnvironment): Promise<void> {
66+
async openExtensionDevelopmentHostWindow(args: string[]): Promise<void> {
67+
if (!this.workspaceProvider.environment) {
68+
// TODO@Ben remove me once environment is adopted
69+
return this.openExtensionDevelopmentHostWindowLegacy(args);
70+
}
71+
72+
// Find out which workspace to open debug window on
73+
let debugWorkspace: IWorkspace = undefined;
74+
const folderUriArg = this.findArgument('folder-uri', args);
75+
if (folderUriArg) {
76+
debugWorkspace = { folderUri: URI.parse(folderUriArg) };
77+
}
78+
79+
// Add environment parameters required for debug to work
80+
const environment = new Map<string, string>();
81+
82+
const extensionDevelopmentPath = this.findArgument('extensionDevelopmentPath', args);
83+
if (extensionDevelopmentPath) {
84+
environment.set('extensionDevelopmentPath', extensionDevelopmentPath);
85+
}
86+
87+
const debugId = this.findArgument('debugId', args);
88+
if (debugId) {
89+
environment.set('debugId', debugId);
90+
}
91+
92+
const inspectBrkExtensions = this.findArgument('inspect-brk-extensions', args);
93+
if (inspectBrkExtensions) {
94+
environment.set('inspect-brk-extensions', inspectBrkExtensions);
95+
}
96+
97+
// Open debug window as new window. Pass ParsedArgs over.
98+
this.workspaceProvider.open(debugWorkspace, {
99+
reuse: false, // debugging always requires a new window
100+
environment // mandatory properties to enable debugging
101+
});
102+
}
103+
104+
private async openExtensionDevelopmentHostWindowLegacy(args: string[]): Promise<void> {
51105
// we pass the "args" as query parameters of the URL
52106

53107
let newAddress = `${document.location.origin}${document.location.pathname}?`;
@@ -73,7 +127,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
73127

74128
const f = findArgument('folder-uri');
75129
if (f) {
76-
const u = URI.parse(f[0]);
130+
const u = URI.parse(f);
77131
gotFolder = true;
78132
addQueryParameter('folder', u.path);
79133
}
@@ -84,24 +138,34 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i
84138

85139
const ep = findArgument('extensionDevelopmentPath');
86140
if (ep) {
87-
let u = ep[0];
88-
addQueryParameter('edp', u);
141+
addQueryParameter('extensionDevelopmentPath', ep);
89142
}
90143

91144
const di = findArgument('debugId');
92145
if (di) {
93-
addQueryParameter('di', di);
146+
addQueryParameter('debugId', di);
94147
}
95148

96149
const ibe = findArgument('inspect-brk-extensions');
97150
if (ibe) {
98-
addQueryParameter('ibe', ibe);
151+
addQueryParameter('inspect-brk-extensions', ibe);
99152
}
100153

101154
window.open(newAddress);
102155

103156
return Promise.resolve();
104157
}
158+
159+
private findArgument(key: string, args: string[]): string | undefined {
160+
for (const a of args) {
161+
const k = `--${key}=`;
162+
if (a.indexOf(k) === 0) {
163+
return a.substr(k.length);
164+
}
165+
}
166+
167+
return undefined;
168+
}
105169
}
106170

107171
registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService);

src/vs/workbench/services/environment/browser/environmentService.ts

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,32 +100,53 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
100100

101101
this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' });
102102

103-
if (document && document.location && document.location.search) {
104-
const map = new Map<string, string>();
105-
const query = document.location.search.substring(1);
106-
const vars = query.split('&');
107-
for (let p of vars) {
108-
const pair = p.split('=');
109-
if (pair.length >= 2) {
110-
map.set(pair[0], decodeURIComponent(pair[1]));
103+
// Fill in selected extra environmental properties
104+
if (options.workspaceProvider && options.workspaceProvider.environment) {
105+
const environment = options.workspaceProvider.environment;
106+
for (const [key, value] of environment) {
107+
switch (key) {
108+
case 'extensionDevelopmentPath':
109+
this.extensionDevelopmentLocationURI = [URI.parse(value)];
110+
this.isExtensionDevelopment = true;
111+
break;
112+
case 'debugId':
113+
this.debugExtensionHost.debugId = value;
114+
break;
115+
case 'inspect-brk-extensions':
116+
this.debugExtensionHost.port = parseInt(value);
117+
this.debugExtensionHost.break = false;
118+
break;
111119
}
112120
}
121+
} else {
122+
// TODO@Ben remove me once environment is adopted
123+
if (document && document.location && document.location.search) {
124+
const map = new Map<string, string>();
125+
const query = document.location.search.substring(1);
126+
const vars = query.split('&');
127+
for (let p of vars) {
128+
const pair = p.split('=');
129+
if (pair.length >= 2) {
130+
map.set(pair[0], decodeURIComponent(pair[1]));
131+
}
132+
}
113133

114-
const edp = map.get('edp');
115-
if (edp) {
116-
this.extensionDevelopmentLocationURI = [URI.parse(edp)];
117-
this.isExtensionDevelopment = true;
118-
}
134+
const edp = map.get('extensionDevelopmentPath');
135+
if (edp) {
136+
this.extensionDevelopmentLocationURI = [URI.parse(edp)];
137+
this.isExtensionDevelopment = true;
138+
}
119139

120-
const di = map.get('di');
121-
if (di) {
122-
this.debugExtensionHost.debugId = di;
123-
}
140+
const di = map.get('debugId');
141+
if (di) {
142+
this.debugExtensionHost.debugId = di;
143+
}
124144

125-
const ibe = map.get('ibe');
126-
if (ibe) {
127-
this.debugExtensionHost.port = parseInt(ibe);
128-
this.debugExtensionHost.break = false;
145+
const ibe = map.get('inspect-brk-extensions');
146+
if (ibe) {
147+
this.debugExtensionHost.port = parseInt(ibe);
148+
this.debugExtensionHost.break = false;
149+
}
129150
}
130151
}
131152
}

src/vs/workbench/services/host/browser/browserHostService.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,22 @@ export interface IWorkspaceProvider {
3333
*/
3434
readonly workspace: IWorkspace;
3535

36+
/**
37+
* Optionally a set of environmental properties to be used
38+
* as environment for the workspace to open.
39+
*/
40+
readonly environment?: ReadonlyMap<string, string>;
41+
3642
/**
3743
* Asks to open a workspace in the current or a new window.
3844
*
3945
* @param workspace the workspace to open.
40-
* @param options wether to open inside the current window or a new window.
46+
* @param options optional options for the workspace to open.
47+
* - `reuse`: wether to open inside the current window or a new window
48+
* - `environment`: a map of environmental properties that should be
49+
* filled into the environment of the workspace to open.
4150
*/
42-
open(workspace: IWorkspace, options?: { reuse?: boolean }): Promise<void>;
51+
open(workspace: IWorkspace, options?: { reuse?: boolean, environment?: Map<string, string> }): Promise<void>;
4352
}
4453

4554
export class BrowserHostService extends Disposable implements IHostService {

src/vs/workbench/workbench.web.api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ interface IWorkbenchConstructionOptions {
8282
*/
8383
logLevel?: LogLevel;
8484

85-
8685
/**
8786
* Experimental: Support for update reporting.
8887
*/

0 commit comments

Comments
 (0)