55 BrowserWindow ,
66 autoUpdater ,
77 nativeTheme ,
8+ ipcMain as electronIpcMain ,
89} from 'electron'
10+ import { IpcMainEvent } from 'electron/main'
911import { shell } from '../lib/app-shell'
1012import { Emitter , Disposable } from 'event-kit'
1113import { encodePathAsUrl } from '../lib/path'
@@ -20,19 +22,16 @@ import { menuFromElectronMenu } from '../models/app-menu'
2022import { now } from './now'
2123import * as path from 'path'
2224import windowStateKeeper from 'electron-window-state'
23- import * as ipcMain from './ipc-main'
2425import * as ipcWebContents from './ipc-webcontents'
25- import {
26- installNotificationCallback ,
27- terminateDesktopNotifications ,
28- } from './notifications'
26+ import { installNotificationCallback } from './notifications'
2927import { addTrustedIPCSender } from './trusted-ipc-sender'
3028import { getUpdaterGUID } from '../lib/get-updater-guid'
3129import { CLIAction } from '../lib/cli-action'
3230
3331export class AppWindow {
3432 private window : Electron . BrowserWindow
3533 private emitter = new Emitter ( )
34+ private readonly cleanupTasks = new Array < ( ) => void > ( )
3635
3736 private _loadTime : number | null = null
3837 private _rendererReadyTime : number | null = null
@@ -43,6 +42,8 @@ export class AppWindow {
4342
4443 // See https://github.com/desktop/desktop/pull/11162
4544 private shouldMaximizeOnShow = false
45+ private quitting = false
46+ private quittingEvenIfUpdating = false
4647
4748 public constructor ( ) {
4849 const savedWindowState = windowStateKeeper ( {
@@ -84,41 +85,24 @@ export class AppWindow {
8485 this . window = new BrowserWindow ( windowOptions )
8586 addTrustedIPCSender ( this . window . webContents )
8687
87- installNotificationCallback ( this . window )
88+ this . addCleanupTask ( installNotificationCallback ( this . window ) )
8889
8990 savedWindowState . manage ( this . window )
9091 this . shouldMaximizeOnShow = savedWindowState . isMaximized
9192
92- let quitting = false
93- let quittingEvenIfUpdating = false
94- app . on ( 'before-quit' , ( ) => {
95- quitting = true
96- } )
97-
98- ipcMain . on ( 'will-quit' , event => {
99- quitting = true
100- event . returnValue = true
101- } )
102-
103- ipcMain . on ( 'will-quit-even-if-updating' , event => {
104- quitting = true
105- quittingEvenIfUpdating = true
106- event . returnValue = true
107- } )
108-
109- ipcMain . on ( 'cancel-quitting' , event => {
110- quitting = false
111- quittingEvenIfUpdating = false
112- event . returnValue = true
113- } )
93+ const onBeforeQuit = ( ) => {
94+ this . quitting = true
95+ }
96+ app . on ( 'before-quit' , onBeforeQuit )
97+ this . addCleanupTask ( ( ) => app . removeListener ( 'before-quit' , onBeforeQuit ) )
11498
11599 this . window . on ( 'close' , e => {
116100 // On macOS, closing the window doesn't mean the app is quitting. If the
117101 // app is updating, we will prevent the window from closing only when the
118102 // app is also quitting.
119103 if (
120- ( ! __DARWIN__ || quitting ) &&
121- ! quittingEvenIfUpdating &&
104+ ( ! __DARWIN__ || this . quitting ) &&
105+ ! this . quittingEvenIfUpdating &&
122106 this . isDownloadingUpdate
123107 ) {
124108 e . preventDefault ( )
@@ -136,7 +120,7 @@ export class AppWindow {
136120 // on macOS, when the user closes the window we really just hide it. This
137121 // lets us activate quickly and keep all our interesting logic in the
138122 // renderer.
139- if ( __DARWIN__ && ! quitting ) {
123+ if ( __DARWIN__ && ! this . quitting ) {
140124 e . preventDefault ( )
141125 // https://github.com/desktop/desktop/issues/12838
142126 if ( this . window . isFullScreen ( ) ) {
@@ -147,10 +131,9 @@ export class AppWindow {
147131 }
148132 return
149133 }
150- nativeTheme . removeAllListeners ( )
151- autoUpdater . removeAllListeners ( )
152- terminateDesktopNotifications ( )
153134 } )
135+
136+ this . window . on ( 'closed' , ( ) => this . cleanup ( ) )
154137 }
155138
156139 public load ( ) {
@@ -187,11 +170,37 @@ export class AppWindow {
187170 this . window . show ( )
188171 } )
189172
190- // TODO: This should be scoped by the window.
191- ipcMain . once ( 'renderer-ready' , ( _ , readyTime ) => {
173+ const onRendererReady = ( event : IpcMainEvent , readyTime : number ) => {
174+ if ( event . sender !== this . window . webContents ) {
175+ return
176+ }
177+
192178 this . _rendererReadyTime = readyTime
193179 this . maybeEmitDidLoad ( )
194- } )
180+ electronIpcMain . removeListener ( 'renderer-ready' , onRendererReady )
181+ }
182+ electronIpcMain . on ( 'renderer-ready' , onRendererReady )
183+ this . addCleanupTask ( ( ) =>
184+ electronIpcMain . removeListener ( 'renderer-ready' , onRendererReady )
185+ )
186+
187+ const onBackgroundColorUpdated = ( event : IpcMainEvent , color : string ) => {
188+ if ( event . sender !== this . window . webContents ) {
189+ return
190+ }
191+
192+ this . window . setBackgroundColor ( color )
193+ }
194+ electronIpcMain . on (
195+ 'update-window-background-color' ,
196+ onBackgroundColorUpdated
197+ )
198+ this . addCleanupTask ( ( ) =>
199+ electronIpcMain . removeListener (
200+ 'update-window-background-color' ,
201+ onBackgroundColorUpdated
202+ )
203+ )
195204
196205 this . window . on ( 'focus' , ( ) =>
197206 ipcWebContents . send ( this . window . webContents , 'focus' )
@@ -203,13 +212,13 @@ export class AppWindow {
203212 registerWindowStateChangedEvents ( this . window )
204213 this . window . loadURL ( encodePathAsUrl ( __dirname , 'index.html' ) )
205214
206- nativeTheme . addListener ( 'updated' , ( ) => {
215+ const onNativeThemeUpdated = ( ) => {
207216 ipcWebContents . send ( this . window . webContents , 'native-theme-updated' )
208- } )
209-
210- ipcMain . on ( 'update-window-background-color' , ( _ , color ) => {
211- this . window . setBackgroundColor ( color )
212- } )
217+ }
218+ nativeTheme . addListener ( 'updated' , onNativeThemeUpdated )
219+ this . addCleanupTask ( ( ) =>
220+ nativeTheme . removeListener ( 'updated' , onNativeThemeUpdated )
221+ )
213222
214223 this . setupAutoUpdater ( )
215224 }
@@ -231,6 +240,10 @@ export class AppWindow {
231240 return ! ! this . loadTime && ! ! this . rendererReadyTime
232241 }
233242
243+ public get isLoaded ( ) : boolean {
244+ return this . rendererLoaded
245+ }
246+
234247 public onClosed ( fn : ( ) => void ) {
235248 this . window . on ( 'closed' , fn )
236249 }
@@ -260,10 +273,31 @@ export class AppWindow {
260273 return this . window . isFocused ( )
261274 }
262275
276+ public get id ( ) {
277+ return this . window . id
278+ }
279+
263280 public focus ( ) {
264281 this . window . focus ( )
265282 }
266283
284+ public revealAndFocus ( ) {
285+ if ( this . window . isMinimized ( ) ) {
286+ this . window . restore ( )
287+ }
288+
289+ if ( ! this . window . isVisible ( ) ) {
290+ this . show ( )
291+ return
292+ }
293+
294+ this . window . focus ( )
295+ }
296+
297+ public setTitle ( title : string ) {
298+ this . window . setTitle ( title )
299+ }
300+
267301 /** Selects all the windows web contents */
268302 public selectAllWindowContents ( ) {
269303 this . window . webContents . selectAll ( )
@@ -394,42 +428,60 @@ export class AppWindow {
394428 }
395429
396430 public setupAutoUpdater ( ) {
397- autoUpdater . on ( 'error' , ( error : Error ) => {
431+ const onAutoUpdaterError = ( error : Error ) => {
398432 this . isDownloadingUpdate = false
399433 ipcWebContents . send ( this . window . webContents , 'auto-updater-error' , error )
400- } )
434+ }
435+ autoUpdater . on ( 'error' , onAutoUpdaterError )
436+ this . addCleanupTask ( ( ) => autoUpdater . removeListener ( 'error' , onAutoUpdaterError ) )
401437
402- autoUpdater . on ( 'checking-for-update' , ( ) => {
438+ const onCheckingForUpdate = ( ) => {
403439 this . isDownloadingUpdate = false
404440 ipcWebContents . send (
405441 this . window . webContents ,
406442 'auto-updater-checking-for-update'
407443 )
408- } )
444+ }
445+ autoUpdater . on ( 'checking-for-update' , onCheckingForUpdate )
446+ this . addCleanupTask ( ( ) =>
447+ autoUpdater . removeListener ( 'checking-for-update' , onCheckingForUpdate )
448+ )
409449
410- autoUpdater . on ( 'update-available' , ( ) => {
450+ const onUpdateAvailable = ( ) => {
411451 this . isDownloadingUpdate = true
412452 ipcWebContents . send (
413453 this . window . webContents ,
414454 'auto-updater-update-available'
415455 )
416- } )
456+ }
457+ autoUpdater . on ( 'update-available' , onUpdateAvailable )
458+ this . addCleanupTask ( ( ) =>
459+ autoUpdater . removeListener ( 'update-available' , onUpdateAvailable )
460+ )
417461
418- autoUpdater . on ( 'update-not-available' , ( ) => {
462+ const onUpdateNotAvailable = ( ) => {
419463 this . isDownloadingUpdate = false
420464 ipcWebContents . send (
421465 this . window . webContents ,
422466 'auto-updater-update-not-available'
423467 )
424- } )
468+ }
469+ autoUpdater . on ( 'update-not-available' , onUpdateNotAvailable )
470+ this . addCleanupTask ( ( ) =>
471+ autoUpdater . removeListener ( 'update-not-available' , onUpdateNotAvailable )
472+ )
425473
426- autoUpdater . on ( 'update-downloaded' , ( ) => {
474+ const onUpdateDownloaded = ( ) => {
427475 this . isDownloadingUpdate = false
428476 ipcWebContents . send (
429477 this . window . webContents ,
430478 'auto-updater-update-downloaded'
431479 )
432- } )
480+ }
481+ autoUpdater . on ( 'update-downloaded' , onUpdateDownloaded )
482+ this . addCleanupTask ( ( ) =>
483+ autoUpdater . removeListener ( 'update-downloaded' , onUpdateDownloaded )
484+ )
433485 }
434486
435487 public async checkForUpdates ( url : string ) {
@@ -496,6 +548,30 @@ export class AppWindow {
496548 const { filePaths } = await dialog . showOpenDialog ( this . window , options )
497549 return filePaths . length > 0 ? filePaths [ 0 ] : null
498550 }
551+
552+ public markWillQuit ( ) {
553+ this . quitting = true
554+ }
555+
556+ public markWillQuitEvenIfUpdating ( ) {
557+ this . quitting = true
558+ this . quittingEvenIfUpdating = true
559+ }
560+
561+ public cancelQuitting ( ) {
562+ this . quitting = false
563+ this . quittingEvenIfUpdating = false
564+ }
565+
566+ private addCleanupTask ( task : ( ) => void ) {
567+ this . cleanupTasks . push ( task )
568+ }
569+
570+ private cleanup ( ) {
571+ for ( const task of this . cleanupTasks . splice ( 0 ) . reverse ( ) ) {
572+ task ( )
573+ }
574+ }
499575}
500576
501577const trySetUpdaterGuid = async ( url : string ) => {
0 commit comments