From f5de259e7a1ef064a1b39e144fdfc15593f74ac6 Mon Sep 17 00:00:00 2001 From: Robert Knight Date: Mon, 4 Nov 2024 13:57:59 +0000 Subject: [PATCH] Ignore "fake" window unload events in PortRPC In VitalSource custom/synthetic "unload" events are dispatched at the book's container frame when switching chapters. Make sure these don't trigger the code in the `PortRPC` class that is meant to be called when the window is unloaded. These synthetic events were triggered after every chapter navigation. In Safari <= 15 the second and subsequent events would cause an error in the `currentWindow.parent.postMessage` call because the port had already been transferred after the first event was handled. --- src/shared/messaging/port-rpc.ts | 10 +++++++++- src/shared/messaging/test/port-rpc-test.js | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/shared/messaging/port-rpc.ts b/src/shared/messaging/port-rpc.ts index f4bd984a4b2..fb76a095a77 100644 --- a/src/shared/messaging/port-rpc.ts +++ b/src/shared/messaging/port-rpc.ts @@ -236,7 +236,15 @@ export class PortRPC // send the "close" event through the message channel when the window // containing the sending port is unloaded. if (!('onclose' in MessagePort.prototype) || forceUnloadListener) { - this._listeners.add(currentWindow, 'unload', () => { + this._listeners.add(currentWindow, 'unload', event => { + // Ignore custom events which use the same name. This works around an + // issue in VitalSource. + // + // See https://github.com/hypothesis/support/issues/161#issuecomment-2454560641. + if (event instanceof CustomEvent) { + return; + } + if (this._port) { // Send "close" notification directly. This works in Chrome, Firefox and // Safari >= 16. diff --git a/src/shared/messaging/test/port-rpc-test.js b/src/shared/messaging/test/port-rpc-test.js index 6d3bcba68ef..ee579ba1b44 100644 --- a/src/shared/messaging/test/port-rpc-test.js +++ b/src/shared/messaging/test/port-rpc-test.js @@ -260,6 +260,25 @@ describe('PortRPC', () => { assert.calledWith(closeHandler); }); + // See https://github.com/hypothesis/support/issues/161#issuecomment-2454560641 + it('ignores "fake" window unload events', async () => { + const { port1, port2 } = new MessageChannel(); + const sender = new PortRPC({ forceUnloadListener: true }); + const receiver = new PortRPC(); + const closeHandler = sinon.stub(); + + receiver.on('close', closeHandler); + receiver.connect(port2); + sender.connect(port1); + await waitForMessageDelivery(); + + assert.notCalled(closeHandler); + window.dispatchEvent(new CustomEvent('unload')); + await waitForMessageDelivery(); + + assert.notCalled(closeHandler); + }); + it('should send "close" event when MessagePort emits "close" event', async () => { const { port1, port2 } = new MessageChannel(); const sender = new PortRPC();