Commit f18218f7 authored by Robert Knight's avatar Robert Knight

Unload client from iframes when it is unloaded in the host frame

When the client is unloaded in a frame H, ensure that it is also unloaded from
guest frames whose host frame is H. This enables toggling the browser
extension's active state to work properly in VitalSource Bookshelf and other
pages which have annotation-enabled iframes.

This works by having the `Guest` instance in the iframe listen for the existing
"close" message from its connection to the host frame, and relaying that to the
entry point via a "hostDisconnected" event, which then handles it in the same
way as if the client had been unloaded directly in the guest frame.
parent 93fb3f68
import { TinyEmitter } from 'tiny-emitter';
import { ListenerCollection } from '../shared/listener-collection';
import { PortFinder, PortRPC } from '../shared/messaging';
import { generateHexString } from '../shared/random';
......@@ -119,7 +121,7 @@ export type GuestConfig = {
* each frame connects to the sidebar and host frames as part of its
* initialization.
*/
export class Guest implements Annotator, Destroyable {
export class Guest extends TinyEmitter implements Annotator, Destroyable {
public element: HTMLElement;
/** Ranges of the current text selection. */
......@@ -193,6 +195,8 @@ export class Guest implements Annotator, Destroyable {
config: GuestConfig = {},
hostFrame: Window = window
) {
super();
this.element = element;
this._hostFrame = hostFrame;
this._highlightsVisible = false;
......@@ -401,6 +405,8 @@ export class Guest implements Annotator, Destroyable {
}
});
this._hostRPC.on('close', () => this.emit('hostDisconnected'));
// Discover and connect to the host frame. All RPC events must be
// registered before creating the channel.
const hostPort = await this._portFinder.discover('host');
......
......@@ -50,6 +50,12 @@ const sidebarLinkElement = document.querySelector(
function init() {
const annotatorConfig = getConfig('annotator') as GuestConfig & InjectConfig;
let resolveUnloadRequested = () => {};
const unloadRequested = new Promise<void>(resolve => {
resolveUnloadRequested = resolve;
});
sidebarLinkElement.addEventListener('destroy', resolveUnloadRequested);
const hostFrame = annotatorConfig.subFrameIdentifier ? window.parent : window;
const destroyables = [] as Destroyable[];
......@@ -88,12 +94,18 @@ function init() {
document.body,
annotatorConfig
);
// Create the guest that handles creating annotations and displaying highlights.
const guest = new Guest(document.body, annotatorConfig, hostFrame);
// When the client is unloaded in the host frame, also unload it from any
// connected iframes.
guest.on('hostDisconnected', resolveUnloadRequested);
destroyables.push(hypothesisInjector, guest);
}
sidebarLinkElement.addEventListener('destroy', () => {
unloadRequested.then(() => {
destroyables.forEach(instance => instance.destroy());
// Remove all the `<link>`, `<script>` and `<style>` elements added to the
......
......@@ -1363,6 +1363,16 @@ describe('Guest', () => {
});
});
it('emits "hostDisconnected" event when host frame closes connection with guest', () => {
const guest = createGuest();
const hostDisconnected = sinon.stub();
guest.on('hostDisconnected', hostDisconnected);
emitHostEvent('close');
assert.called(hostDisconnected);
});
it('discovers and creates a channel for communication with the sidebar', async () => {
const { port1 } = new MessageChannel();
fakePortFinder.discover.resolves(port1);
......
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