diff --git a/__test__/unit/sw/serviceWorker.test.ts b/__test__/unit/sw/serviceWorker.test.ts new file mode 100644 index 000000000..de45e11ed --- /dev/null +++ b/__test__/unit/sw/serviceWorker.test.ts @@ -0,0 +1,53 @@ +import { ServiceWorker } from '../../../src/sw/serviceWorker/ServiceWorker'; + +// suppress all internal logging +jest.mock('../../../src/shared/libraries/Log'); + +function chromeUserAgentDataBrands(): Array<{ + brand: string; + version: string; +}> { + return [ + { brand: 'Google Chrome', version: '129' }, + { brand: 'Not=A?Brand', version: '8' }, + { brand: 'Chromium', version: '129' }, + ]; +} + +describe('ServiceWorker', () => { + describe('requiresMacOS15ChromiumAfterDisplayWorkaround', () => { + test('navigator.userAgentData undefined', async () => { + delete (navigator as any).userAgentData; + expect( + ServiceWorker.requiresMacOS15ChromiumAfterDisplayWorkaround(), + ).toBe(false); + }); + test('navigator.userAgentData null', async () => { + (navigator as any).userAgentData = null; + expect( + ServiceWorker.requiresMacOS15ChromiumAfterDisplayWorkaround(), + ).toBe(false); + }); + + test('navigator.userAgentData Chrome on Windows Desktop', async () => { + (navigator as any).userAgentData = { + mobile: false, + platform: 'Windows', + brands: chromeUserAgentDataBrands(), + }; + expect( + ServiceWorker.requiresMacOS15ChromiumAfterDisplayWorkaround(), + ).toBe(false); + }); + test('navigator.userAgentData Chrome on macOS', async () => { + (navigator as any).userAgentData = { + mobile: false, + platform: 'macOS', + brands: chromeUserAgentDataBrands(), + }; + expect( + ServiceWorker.requiresMacOS15ChromiumAfterDisplayWorkaround(), + ).toBe(true); + }); + }); +}); diff --git a/src/sw/serviceWorker/ServiceWorker.ts b/src/sw/serviceWorker/ServiceWorker.ts index 622994a7c..0c988310a 100755 --- a/src/sw/serviceWorker/ServiceWorker.ts +++ b/src/sw/serviceWorker/ServiceWorker.ts @@ -773,10 +773,27 @@ export class ServiceWorker { badge: notification.badgeIcon, }; - return self.registration.showNotification( + await self.registration.showNotification( notification.title, notificationOptions, ); + + if (this.requiresMacOS15ChromiumAfterDisplayWorkaround()) { + await awaitableTimeout(1_000); + } + } + + // Workaround: For Chromium browsers displaying an extra notification, even + // when background rules are followed. + // For reference, the notification body is "This site has been updated in the background". + // https://issues.chromium.org/issues/378103918 + static requiresMacOS15ChromiumAfterDisplayWorkaround(): boolean { + const userAgentData = (navigator as any).userAgentData; + const isMacOS = userAgentData?.platform === 'macOS'; + const isChromium = !!userAgentData?.brands?.some( + (item: { brand: string }) => item.brand === 'Chromium', + ); + return isMacOS && isChromium; } /**