Avoid calling `tray.destroy()` when quitting

This commit is contained in:
Fedor Indutny 2022-01-24 16:18:53 -08:00 committed by GitHub
parent dff941adc7
commit 3aa488c3d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 3 deletions

View File

@ -7,6 +7,13 @@ import { Menu, Tray, app, nativeImage } from 'electron';
import * as log from '../ts/logging/log';
import type { LocaleMessagesType } from '../ts/types/I18N';
export type SystemTrayServiceOptionsType = Readonly<{
messages: LocaleMessagesType;
// For testing
createTrayInstance?: (icon: NativeImage) => Tray;
}>;
/**
* A class that manages an [Electron `Tray` instance][0]. It's responsible for creating
* and destroying a `Tray`, and listening to the associated `BrowserWindow`'s visibility
@ -23,14 +30,19 @@ export class SystemTrayService {
private isEnabled = false;
private isQuitting = false;
private unreadCount = 0;
private boundRender: typeof SystemTrayService.prototype.render;
constructor({ messages }: Readonly<{ messages: LocaleMessagesType }>) {
private createTrayInstance: (icon: NativeImage) => Tray;
constructor({ messages, createTrayInstance }: SystemTrayServiceOptionsType) {
log.info('System tray service: created');
this.messages = messages;
this.boundRender = this.render.bind(this);
this.createTrayInstance = createTrayInstance || (icon => new Tray(icon));
}
/**
@ -92,6 +104,19 @@ export class SystemTrayService {
this.render();
}
/**
* Workaround for: https://github.com/electron/electron/issues/32581#issuecomment-1020359931
*
* Tray is automatically destroyed when app quits so we shouldn't destroy it
* twice when all windows will close.
*/
markShouldQuit(): void {
log.info('System tray service: markShouldQuit');
this.tray = undefined;
this.isQuitting = true;
}
private render(): void {
if (this.isEnabled && this.browserWindow) {
this.renderEnabled();
@ -101,6 +126,11 @@ export class SystemTrayService {
}
private renderEnabled() {
if (this.isQuitting) {
log.info('System tray service: not rendering the tray, quitting');
return;
}
log.info('System tray service: rendering the tray');
this.tray = this.tray || this.createTray();
@ -177,7 +207,7 @@ export class SystemTrayService {
log.info('System tray service: creating the tray');
// This icon may be swiftly overwritten.
const result = new Tray(getDefaultIcon());
const result = this.createTrayInstance(getDefaultIcon());
// Note: "When app indicator is used on Linux, the click event is ignored." This
// doesn't mean that the click event is always ignored on Linux; it depends on how

View File

@ -1729,6 +1729,7 @@ app.on('before-quit', () => {
shouldQuit: windowState.shouldQuit(),
});
systemTrayService?.markShouldQuit();
windowState.markShouldQuit();
if (mainWindow) {

View File

@ -8,6 +8,7 @@ import { BrowserWindow, Tray, nativeImage } from 'electron';
import * as path from 'path';
import { MINUTE } from '../../util/durations';
import type { SystemTrayServiceOptionsType } from '../../../app/SystemTrayService';
import { SystemTrayService } from '../../../app/SystemTrayService';
describe('SystemTrayService', function thisNeeded() {
@ -23,7 +24,9 @@ describe('SystemTrayService', function thisNeeded() {
*
* This only affects these tests, not the "real" code.
*/
function newService(): SystemTrayService {
function newService(
options?: Partial<SystemTrayServiceOptionsType>
): SystemTrayService {
const result = new SystemTrayService({
messages: {
hide: { message: 'Hide' },
@ -31,6 +34,7 @@ describe('SystemTrayService', function thisNeeded() {
show: { message: 'Show' },
signalDesktop: { message: 'Signal' },
},
...options,
});
servicesCreated.add(result);
return result;
@ -258,4 +262,16 @@ describe('SystemTrayService', function thisNeeded() {
sinon.assert.calledWith(setImageStub, sinon.match.string);
sinon.assert.calledWith(setImageStub, sinon.match.instanceOf(NativeImage));
});
it('should not create new Tray after markShouldQuit', () => {
const createTrayInstance = sandbox.stub();
const service = newService({ createTrayInstance });
service.setMainWindow(new BrowserWindow({ show: false }));
service.markShouldQuit();
service.setEnabled(true);
sinon.assert.notCalled(createTrayInstance);
});
});