in src/vs/code/electron-main/app.ts [114:308]
private registerListeners(): void {
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
setUnexpectedErrorHandler(err => this.onUnexpectedError(err));
process.on('uncaughtException', err => this.onUnexpectedError(err));
process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason));
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
// Contextmenu via IPC support
registerContextMenuListener();
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled);
}
});
app.on('activate', (event: Event, hasVisibleWindows: boolean) => {
this.logService.trace('App#activate');
// Mac only event: open new window when we get activated
if (!hasVisibleWindows && this.windowsMainService) {
this.windowsMainService.openNewWindow(OpenContext.DOCK);
}
});
// Security related measures (https://electronjs.org/docs/tutorial/security)
//
// !!! DO NOT CHANGE without consulting the documentation !!!
//
// app.on('remote-get-guest-web-contents', event => event.preventDefault()); // TODO@Ben TODO@Matt revisit this need for <webview>
app.on('remote-require', (event, sender, module) => {
this.logService.trace('App#on(remote-require): prevented');
event.preventDefault();
});
app.on('remote-get-global', (event, sender, module) => {
this.logService.trace(`App#on(remote-get-global): prevented on ${module}`);
event.preventDefault();
});
app.on('remote-get-builtin', (event, sender, module) => {
this.logService.trace(`App#on(remote-get-builtin): prevented on ${module}`);
if (module !== 'clipboard') {
event.preventDefault();
}
});
app.on('remote-get-current-window', event => {
this.logService.trace(`App#on(remote-get-current-window): prevented`);
event.preventDefault();
});
app.on('remote-get-current-web-contents', event => {
this.logService.trace(`App#on(remote-get-current-web-contents): prevented`);
event.preventDefault();
});
app.on('web-contents-created', (_event: Electron.Event, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
const isValidWebviewSource = (source: string): boolean => {
if (!source) {
return false;
}
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
return true;
}
const srcUri = URI.parse(source).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
return startsWith(srcUri, rootUri + sep);
};
// Ensure defaults
delete webPreferences.preload;
webPreferences.nodeIntegration = false;
// Verify URLs being loaded
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) {
return;
}
delete webPreferences.preloadUrl;
// Otherwise prevent loading
this.logService.error('webContents#web-contents-created: Prevented webview attach');
event.preventDefault();
});
contents.on('will-navigate', event => {
this.logService.error('webContents#will-navigate: Prevented webcontent navigation');
event.preventDefault();
});
contents.on('new-window', (event: Event, url: string) => {
event.preventDefault(); // prevent code that wants to open links
shell.openExternal(url);
});
});
let macOpenFileURIs: IURIToOpen[] = [];
let runningTimeout: NodeJS.Timeout | null = null;
app.on('open-file', (event: Event, path: string) => {
this.logService.trace('App#open-file: ', path);
event.preventDefault();
// Keep in array because more might come!
macOpenFileURIs.push(this.getURIToOpenFromPathSync(path));
// Clear previous handler if any
if (runningTimeout !== null) {
clearTimeout(runningTimeout);
runningTimeout = null;
}
// Handle paths delayed in case more are coming!
runningTimeout = setTimeout(() => {
if (this.windowsMainService) {
this.windowsMainService.open({
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
cli: this.environmentService.args,
urisToOpen: macOpenFileURIs,
gotoLineMode: false,
preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */
});
macOpenFileURIs = [];
runningTimeout = null;
}
}, 100);
});
app.on('new-window-for-tab', () => {
if (this.windowsMainService) {
this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button
}
});
ipc.on('vscode:exit', (event: Event, code: number) => {
this.logService.trace('IPC#vscode:exit', code);
this.dispose();
this.lifecycleService.kill(code);
});
ipc.on('vscode:fetchShellEnv', async (event: Event) => {
const webContents = event.sender;
try {
const shellEnv = await getShellEnvironment(this.logService, this.environmentService);
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', shellEnv);
}
} catch (error) {
if (!webContents.isDestroyed()) {
webContents.send('vscode:acceptShellEnv', {});
}
this.logService.error('Error fetching shell env', error);
}
});
ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools());
ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools());
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
// Some listeners after window opened
(async () => {
await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen);
// After waking up from sleep (after window opened)
powerMonitor.on('resume', () => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
// Keyboard layout changes (after window opened)
const nativeKeymap = await import('native-keymap');
nativeKeymap.onDidChangeKeyboardLayout(() => {
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
}
});
})();
}