Skip to content

Commit 94125f9

Browse files
committed
url handlers
fixes microsoft#34404
1 parent 241f82a commit 94125f9

9 files changed

Lines changed: 104 additions & 156 deletions

File tree

src/vs/code/electron-main/app.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { IStateService } from 'vs/platform/state/common/state';
2929
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
3030
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3131
import { IURLService } from 'vs/platform/url/common/url';
32-
import { URLHandlerChannelClient } from 'vs/platform/url/common/urlIpc';
32+
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/common/urlIpc';
3333
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
3434
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
3535
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
@@ -59,7 +59,7 @@ import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
5959
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
6060
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
6161
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
62-
import { ElectronURLListener } from 'vs/platform/url/electron-main/urlService';
62+
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
6363

6464
export class CodeApplication {
6565

@@ -369,6 +369,10 @@ export class CodeApplication {
369369
this.electronIpcServer.registerChannel('windows', windowsChannel);
370370
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
371371

372+
const urlService = accessor.get(IURLService);
373+
const urlChannel = new URLServiceChannel(urlService);
374+
this.electronIpcServer.registerChannel('url', urlChannel);
375+
372376
// Log level management
373377
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
374378
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
@@ -381,13 +385,16 @@ export class CodeApplication {
381385
this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
382386

383387
const args = this.environmentService.args;
384-
const urlService = accessor.get(IURLService);
385388

389+
// Create a URL handler which forwards to the last active window
386390
const activeWindowManager = new ActiveWindowManager(windowsService);
387391
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { route: () => activeWindowManager.activeClientId });
388392
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
393+
394+
// Register the multiple URL handker
389395
urlService.registerHandler(multiplexURLHandler);
390396

397+
// Watch Electron URLs and forward them to the UrlService
391398
const urls = args['open-url'] ? args._urls : [];
392399
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
393400
this.toDispose.push(urlListener);

src/vs/code/electron-main/launch.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ export interface IMainProcessInfo {
4040
windows: IWindowInfo[];
4141
}
4242

43+
function parseOpenUrl(args: ParsedArgs): URI[] {
44+
if (args['open-url'] && args._urls && args._urls.length > 0) {
45+
// --open-url must contain -- followed by the url(s)
46+
// process.argv is used over args._ as args._ are resolved to file paths at this point
47+
return args._urls
48+
.map(url => {
49+
try {
50+
return URI.parse(url);
51+
} catch (err) {
52+
return null;
53+
}
54+
})
55+
.filter(uri => !!uri);
56+
}
57+
58+
return [];
59+
}
60+
4361
export interface ILaunchService {
4462
_serviceBrand: any;
4563
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
@@ -119,36 +137,32 @@ export class LaunchService implements ILaunchService {
119137
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
120138
this.logService.trace('Received data from other instance: ', args, userEnv);
121139

140+
const urlsToOpen = parseOpenUrl(args);
141+
122142
// Check early for open-url which is handled in URL service
123-
if (this.shouldOpenUrl(args)) {
143+
if (urlsToOpen.length) {
144+
let whenWindowReady = TPromise.as<any>(null);
145+
146+
// Create a window if there is none
147+
if (this.windowsMainService.getWindowCount() === 0) {
148+
const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0];
149+
whenWindowReady = window.ready();
150+
}
151+
152+
// Make sure a window is open, ready to receive the url event
153+
whenWindowReady.then(() => {
154+
for (const url of urlsToOpen) {
155+
this.urlService.open(url);
156+
}
157+
});
158+
124159
return TPromise.as(null);
125160
}
126161

127162
// Otherwise handle in windows service
128163
return this.startOpenWindow(args, userEnv);
129164
}
130165

131-
private shouldOpenUrl(args: ParsedArgs): boolean {
132-
if (args['open-url'] && args._urls && args._urls.length > 0) {
133-
// --open-url must contain -- followed by the url(s)
134-
// process.argv is used over args._ as args._ are resolved to file paths at this point
135-
args._urls
136-
.map(url => {
137-
try {
138-
return URI.parse(url);
139-
} catch (err) {
140-
return null;
141-
}
142-
})
143-
.filter(uri => !!uri)
144-
.forEach(uri => this.urlService.open(uri));
145-
146-
return true;
147-
}
148-
149-
return false;
150-
}
151-
152166
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
153167
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
154168
let usedWindows: ICodeWindow[];

src/vs/code/electron-main/windows.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,8 +1374,8 @@ export class WindowsManager implements IWindowsMainService {
13741374
return getLastActiveWindow(WindowsManager.WINDOWS);
13751375
}
13761376

1377-
public openNewWindow(context: OpenContext): void {
1378-
this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
1377+
public openNewWindow(context: OpenContext): ICodeWindow[] {
1378+
return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
13791379
}
13801380

13811381
public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {

src/vs/platform/url/common/urlIpc.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,41 @@
77

88
import { TPromise } from 'vs/base/common/winjs.base';
99
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
10-
import { IURLHandler } from './url';
10+
import { IURLHandler, IURLService } from './url';
1111
import URI from 'vs/base/common/uri';
12+
import { IDisposable } from 'vs/base/common/lifecycle';
13+
14+
export interface IURLServiceChannel extends IChannel {
15+
call(command: 'open', url: string): TPromise<boolean>;
16+
call(command: string, arg?: any): TPromise<any>;
17+
}
18+
19+
export class URLServiceChannel implements IURLServiceChannel {
20+
21+
constructor(private service: IURLService) { }
22+
23+
call(command: string, arg?: any): TPromise<any> {
24+
switch (command) {
25+
case 'open': return this.service.open(URI.revive(arg));
26+
}
27+
return undefined;
28+
}
29+
}
30+
31+
export class URLServiceChannelClient implements IURLService {
32+
33+
_serviceBrand: any;
34+
35+
constructor(private channel: IChannel) { }
36+
37+
open(url: URI): TPromise<boolean, any> {
38+
return this.channel.call('open', url.toJSON());
39+
}
40+
41+
registerHandler(handler: IURLHandler): IDisposable {
42+
throw new Error('Not implemented.');
43+
}
44+
}
1245

1346
export interface IURLHandlerChannel extends IChannel {
1447
call(command: 'handleURL', arg: any): TPromise<boolean>;

src/vs/platform/url/common/urlService.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ export class URLService implements IURLService {
3838
}
3939
}
4040

41-
export class ForwardingURLHandler implements IURLHandler {
41+
export class RelayURLService extends URLService implements IURLHandler {
4242

43-
constructor(@IURLService private urlService: IURLService) { }
43+
constructor(private urlService: IURLService) {
44+
super();
45+
}
4446

45-
handleURL(uri: URI): TPromise<boolean> {
47+
async open(uri: URI): TPromise<boolean> {
4648
return this.urlService.open(uri);
4749
}
50+
51+
handleURL(uri: URI): TPromise<boolean> {
52+
return super.open(uri);
53+
}
4854
}

src/vs/platform/url/electron-main/urlService.ts renamed to src/vs/platform/url/electron-main/electronUrlListener.ts

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@
66
'use strict';
77

88
import { mapEvent, fromNodeEventEmitter, filterEvent } from 'vs/base/common/event';
9-
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
9+
import { IURLService } from 'vs/platform/url/common/url';
1010
import product from 'vs/platform/node/product';
1111
import { app } from 'electron';
1212
import URI from 'vs/base/common/uri';
13-
import { ILogService } from 'vs/platform/log/common/log';
14-
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
15-
import { TPromise } from 'vs/base/common/winjs.base';
16-
import { Limiter } from 'vs/base/common/async';
13+
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1714
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
1815
import { ReadyState } from 'vs/platform/windows/common/windows';
1916

@@ -25,93 +22,6 @@ function uriFromRawUrl(url: string): URI | null {
2522
}
2623
}
2724

28-
export class URLService implements IURLService {
29-
30-
_serviceBrand: any;
31-
32-
private buffer: URI[];
33-
private handlers: IURLHandler[] = [];
34-
private handlerLimiter = new Limiter<void>(1);
35-
private disposables: IDisposable[] = [];
36-
37-
constructor(
38-
initial: string | string[],
39-
@ILogService private logService: ILogService
40-
) {
41-
const globalBuffer = (global.getOpenUrls() || []) as string[];
42-
const rawBuffer = [
43-
...(typeof initial === 'string' ? [initial] : initial),
44-
...globalBuffer
45-
];
46-
47-
this.buffer = rawBuffer.map(uriFromRawUrl).filter(uri => !!uri);
48-
49-
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']);
50-
51-
const onOpenElectronUrl = mapEvent(
52-
fromNodeEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url })),
53-
({ event, url }) => {
54-
// always prevent default and return the url as string
55-
event.preventDefault();
56-
return url;
57-
});
58-
59-
const onOpenUrl = filterEvent(mapEvent(onOpenElectronUrl, uriFromRawUrl), uri => !!uri);
60-
onOpenUrl(this.openURI, this, this.disposables);
61-
}
62-
63-
open(uri: URI): TPromise<boolean> {
64-
// const uri = uriFromRawUrl(url);
65-
66-
// if (!uri) {
67-
// return ;
68-
// }
69-
70-
return this.openURI(uri);
71-
}
72-
73-
private async openURI(uri: URI, handlers = this.handlers): TPromise<boolean> {
74-
this.logService.trace('urlService#handleURI', uri.toString());
75-
76-
for (const handler of handlers) {
77-
if (await handler.handleURL(uri)) {
78-
return true;
79-
}
80-
}
81-
82-
return false;
83-
}
84-
85-
private async flushBuffer(handler: IURLHandler): TPromise<void> {
86-
const buffer = [...this.buffer];
87-
88-
for (const uri of buffer) {
89-
if (await handler.handleURL(uri)) {
90-
this.buffer.splice(this.buffer.indexOf(uri, 1));
91-
}
92-
}
93-
}
94-
95-
registerHandler(handler: IURLHandler): IDisposable {
96-
this.handlers.push(handler);
97-
this.handlerLimiter.queue(() => this.flushBuffer(handler));
98-
99-
return toDisposable(() => {
100-
const index = this.handlers.indexOf(handler);
101-
102-
if (index === -1) {
103-
return;
104-
}
105-
106-
this.handlers.splice(index, 1);
107-
});
108-
}
109-
110-
dispose(): void {
111-
this.disposables = dispose(this.disposables);
112-
}
113-
}
114-
11525
export class ElectronURLListener {
11626

11727
private buffer: URI[] = [];

src/vs/platform/windows/electron-main/windows.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export interface IWindowsMainService {
107107
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow;
108108
getLastActiveWindow(): ICodeWindow;
109109
waitForWindowCloseOrLoad(windowId: number): TPromise<void>;
110-
openNewWindow(context: OpenContext): void;
110+
openNewWindow(context: OpenContext): ICodeWindow[];
111111
sendToFocused(channel: string, ...args: any[]): void;
112112
sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
113113
getFocusedWindow(): ICodeWindow;

src/vs/platform/windows/electron-main/windowsService.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -489,40 +489,18 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable
489489
async handleURL(uri: URI): TPromise<boolean> {
490490
// Catch file URLs
491491
if (uri.authority === Schemas.file && !!uri.path) {
492-
this.openFileForURI(URI.file(uri.fsPath));
493-
return true;
494-
}
495-
496-
// Catch extension URLs when there are no windows open
497-
if (uri => /^extension/.test(uri.path) && this.windowsMainService.getWindowCount() === 0) {
498-
this.openExtensionForURI(uri);
499-
return true;
492+
return this.openFileForURI(URI.file(uri.fsPath));
500493
}
501494

502495
return false;
503496
}
504497

505-
private openFileForURI(uri: URI): TPromise<void> {
498+
private async openFileForURI(uri: URI): TPromise<boolean> {
506499
const cli = assign(Object.create(null), this.environmentService.args, { goto: true });
507500
const pathsToOpen = [uri.fsPath];
508501

509502
this.windowsMainService.open({ context: OpenContext.API, cli, pathsToOpen });
510-
return TPromise.as(null);
511-
}
512-
513-
/**
514-
* This should only fire whenever an extension URL is open
515-
* and there are no windows to handle it.
516-
*/
517-
private async openExtensionForURI(uri: URI): TPromise<void> {
518-
const cli = assign(Object.create(null), this.environmentService.args);
519-
const window = await this.windowsMainService.open({ context: OpenContext.API, cli })[0];
520-
521-
if (!window) {
522-
return;
523-
}
524-
525-
window.win.show();
503+
return true;
526504
}
527505

528506
dispose(): void {

0 commit comments

Comments
 (0)