Skip to content

Commit

Permalink
Ignore "fake" window unload events in PortRPC
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
robertknight committed Nov 4, 2024
1 parent 5627ba9 commit f5de259
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 1 deletion.
10 changes: 9 additions & 1 deletion src/shared/messaging/port-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,15 @@ export class PortRPC<OnMethod extends string, CallMethod extends string>
// 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.
Expand Down
19 changes: 19 additions & 0 deletions src/shared/messaging/test/port-rpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit f5de259

Please sign in to comment.