Skip to content

Commit e716d41

Browse files
committed
Ensure createWindow is never called before the app is ready
1 parent bf51a37 commit e716d41

File tree

2 files changed

+56
-24
lines changed

2 files changed

+56
-24
lines changed

src/index.ts

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ registerContextMenu({
3232

3333
import { reportStartupEvents } from './report-install-event';
3434
import { menu } from './menu';
35+
import { getDeferred } from './util';
3536

3637
const rmRF = promisify(rimraf);
3738

@@ -141,18 +142,6 @@ if (!amMainInstance) {
141142
process.exit(1); // Safe to hard exit - we haven't opened/started anything yet.
142143
}
143144

144-
app.on('ready', () => {
145-
Menu.setApplicationMenu(menu);
146-
});
147-
148-
app.on('window-all-closed', () => {
149-
// On OS X it is common for applications and their menu bar
150-
// to stay active until the user quits explicitly with Cmd + Q
151-
if (process.platform !== 'darwin') {
152-
app.quit();
153-
}
154-
});
155-
156145
let serverKilled = false;
157146
app.on('will-quit', (event) => {
158147
if (server && !serverKilled) {
@@ -186,14 +175,6 @@ if (!amMainInstance) {
186175
}
187176
});
188177

189-
app.on('activate', () => {
190-
// On OS X it's common to re-create a window in the app when the
191-
// dock icon is clicked and there are no other windows open.
192-
if (windows.length === 0) {
193-
createWindow();
194-
}
195-
});
196-
197178
app.on('web-contents-created', (_event, contents) => {
198179
function injectValue(name: string, value: string) {
199180
// Set a variable globally, and self-postmessage it too (to ping
@@ -400,17 +381,47 @@ if (!amMainInstance) {
400381
reportStartupEvents();
401382

402383
cleanupOldServers().catch(console.log)
403-
.then(() => startServer())
404-
.catch((err) => {
384+
.then(() =>
385+
startServer()
386+
).catch((err) => {
405387
console.error('Failed to start server, exiting.', err);
406388

407389
// Hide immediately, shutdown entirely after a brief pause for Sentry
408390
windows.forEach(window => window.hide());
409391
setTimeout(() => process.exit(1), 500);
410392
});
411393

412-
app.on('ready', () => createWindow());
394+
// Use a promise to organize events around 'ready', and ensure they never
395+
// fire before, as Electron will refuse to do various things if they do.
396+
const appReady = getDeferred();
397+
app.on('ready', () => appReady.resolve());
398+
399+
appReady.promise.then(() => {
400+
Menu.setApplicationMenu(menu);
401+
createWindow();
402+
});
403+
413404
// We use a single process instance to manage the server, but we
414405
// do allow multiple windows.
415-
app.on('second-instance', () => createWindow());
406+
app.on('second-instance', () =>
407+
appReady.promise.then(() => createWindow())
408+
);
409+
410+
app.on('activate', () => {
411+
// On OS X it's common to re-create a window in the app when the
412+
// dock icon is clicked and there are no other windows open.
413+
if (windows.length === 0) {
414+
// Wait until the ready event - it's possible that this can fire
415+
// before the app is ready (not sure how) in which case things break!
416+
appReady.promise.then(() => createWindow());
417+
}
418+
});
419+
420+
app.on('window-all-closed', () => {
421+
// On OS X it is common for applications and their menu bar
422+
// to stay active until the user quits explicitly with Cmd + Q
423+
if (process.platform !== 'darwin') {
424+
app.quit();
425+
}
426+
});
416427
}

src/util.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Taken from HTTP Toolkit UI's util/promise.ts:
2+
3+
export interface Deferred<T> {
4+
resolve: (arg: T) => void,
5+
reject: (e?: Error) => void,
6+
promise: Promise<T>
7+
}
8+
9+
export function getDeferred<T = void>(): Deferred<T> {
10+
let resolve: undefined | ((arg: T) => void) = undefined;
11+
let reject: undefined | ((e?: Error) => void) = undefined;
12+
13+
let promise = new Promise<T>((resolveCb, rejectCb) => {
14+
resolve = resolveCb;
15+
reject = rejectCb;
16+
});
17+
18+
// TS thinks we're using these before they're assigned, which is why
19+
// we need the undefined types, and the any here.
20+
return { resolve, reject, promise } as any;
21+
}

0 commit comments

Comments
 (0)