Commit f5de259e authored by Robert Knight's avatar Robert Knight

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.
parent 5627ba92
...@@ -236,7 +236,15 @@ export class PortRPC<OnMethod extends string, CallMethod extends string> ...@@ -236,7 +236,15 @@ export class PortRPC<OnMethod extends string, CallMethod extends string>
// send the "close" event through the message channel when the window // send the "close" event through the message channel when the window
// containing the sending port is unloaded. // containing the sending port is unloaded.
if (!('onclose' in MessagePort.prototype) || forceUnloadListener) { 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) { if (this._port) {
// Send "close" notification directly. This works in Chrome, Firefox and // Send "close" notification directly. This works in Chrome, Firefox and
// Safari >= 16. // Safari >= 16.
......
...@@ -260,6 +260,25 @@ describe('PortRPC', () => { ...@@ -260,6 +260,25 @@ describe('PortRPC', () => {
assert.calledWith(closeHandler); 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 () => { it('should send "close" event when MessagePort emits "close" event', async () => {
const { port1, port2 } = new MessageChannel(); const { port1, port2 } = new MessageChannel();
const sender = new PortRPC(); const sender = new PortRPC();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment