Commit a8b50cf3 authored by Robert Knight's avatar Robert Knight

Implement `Guest.injectClient`

Implement the `injectClient` method of Guest that is used by
integrations (eg. VitalSource) to inject the client into a chosen frame.

In the process the `HypothesisInjector` class has been extracted out of
the `CrossFrame` class, since it is unrelated to the rest of the
functionality in that class and only lived their because of its
dependence on the `Bridge` instance, which will soon be removed (see
https://github.com/hypothesis/client/pull/3812). It is now constructed
and used directly by the `Guest` class instead.
parent 3cb14c36
import { Bridge } from '../shared/bridge';
import { AnnotationSync } from './annotation-sync';
import { HypothesisInjector } from './hypothesis-injector';
/**
* @typedef {import('../shared/port-rpc').PortRPC} RPC
......@@ -24,12 +23,10 @@ export class CrossFrame {
/**
* @param {Element} element
* @param {EventBus} eventBus - Event bus for communicating with the annotator code (eg. the Guest)
* @param {Record<string, any>} config
*/
constructor(element, eventBus, config) {
constructor(element, eventBus) {
this._bridge = new Bridge();
this._annotationSync = new AnnotationSync(eventBus, this._bridge);
this._hypothesisInjector = new HypothesisInjector(element, config);
}
/**
......@@ -58,7 +55,6 @@ export class CrossFrame {
destroy() {
this._bridge.destroy();
this._annotationSync.destroy();
this._hypothesisInjector.destroy();
}
/**
......
......@@ -11,6 +11,7 @@ import {
setHighlightsFocused,
setHighlightsVisible,
} from './highlighter';
import { HypothesisInjector } from './hypothesis-injector';
import { createIntegration } from './integrations';
import * as rangeUtil from './range-util';
import { SelectionObserver } from './selection-observer';
......@@ -157,21 +158,24 @@ export default class Guest {
*/
this.anchors = [];
/**
* Integration that handles document-type specific functionality in the
* guest.
*/
this._integration = createIntegration(this);
// Set the frame identifier if it's available.
// The "top" guest instance will have this as null since it's in a top frame not a sub frame
this._frameIdentifier = config.subFrameIdentifier || null;
// Setup connection to sidebar.
this.crossframe = new CrossFrame(this.element, eventBus, config);
this.crossframe = new CrossFrame(this.element, eventBus);
this.crossframe.onConnect(() => this._setupInitialState(config));
this._hypothesisInjector = new HypothesisInjector(this.element, config);
this._connectSidebarEvents();
/**
* Integration that handles document-type specific functionality in the
* guest.
*/
this._integration = createIntegration(this);
this._sideBySideActive = false;
// Listen for annotations being loaded or unloaded.
......@@ -253,10 +257,8 @@ export default class Guest {
*
* @param {HTMLIFrameElement} frame
*/
// eslint-disable-next-line no-unused-vars
async injectClient(frame) {
/* istanbul ignore next */
console.warn('Guest#injectClient is not yet implemented.');
return this._hypothesisInjector.injectClient(frame);
}
/**
......@@ -358,6 +360,7 @@ export default class Guest {
destroy() {
this._notifyGuestUnload();
this._hypothesisInjector.destroy();
this._listeners.removeAll();
this._selectionObserver.disconnect();
......
......@@ -3,12 +3,10 @@ import { CrossFrame, $imports } from '../cross-frame';
describe('CrossFrame', () => {
let fakeAnnotationSync;
let fakeBridge;
let fakeHypothesisInjector;
let fakeEventBus;
let FakeAnnotationSync;
let FakeBridge;
let FakeHypothesisInjector;
const createCrossFrame = (options = {}) => {
fakeEventBus = {};
......@@ -26,16 +24,13 @@ describe('CrossFrame', () => {
on: sinon.stub(),
};
fakeAnnotationSync = { sync: sinon.stub(), destroy: sinon.stub() };
fakeHypothesisInjector = { destroy: sinon.stub() };
FakeAnnotationSync = sinon.stub().returns(fakeAnnotationSync);
FakeBridge = sinon.stub().returns(fakeBridge);
FakeHypothesisInjector = sinon.stub().returns(fakeHypothesisInjector);
$imports.$mock({
'../shared/bridge': { Bridge: FakeBridge },
'./annotation-sync': { AnnotationSync: FakeAnnotationSync },
'./hypothesis-injector': { HypothesisInjector: FakeHypothesisInjector },
});
});
......@@ -53,11 +48,6 @@ describe('CrossFrame', () => {
createCrossFrame();
assert.calledWith(FakeAnnotationSync, fakeEventBus, fakeBridge);
});
it('initiates the HypothesisInjector service', () => {
createCrossFrame();
assert.calledOnce(FakeHypothesisInjector);
});
});
describe('#connectToSidebar', () => {
......@@ -103,12 +93,6 @@ describe('CrossFrame', () => {
cf.destroy();
assert.called(fakeAnnotationSync.destroy);
});
it('destroys the HypothesisInjector service', () => {
const cf = createCrossFrame();
cf.destroy();
assert.called(fakeHypothesisInjector.destroy);
});
});
describe('#sync', () => {
......
......@@ -43,6 +43,9 @@ describe('Guest', () => {
let fakeCreateIntegration;
let fakeIntegration;
let FakeHypothesisInjector;
let fakeHypothesisInjector;
let guests;
const createGuest = (config = {}) => {
......@@ -101,6 +104,12 @@ describe('Guest', () => {
postMessage: sinon.stub(),
};
fakeHypothesisInjector = {
destroy: sinon.stub(),
injectClient: sinon.stub().resolves(),
};
FakeHypothesisInjector = sinon.stub().returns(fakeHypothesisInjector);
class FakeSelectionObserver {
constructor(callback) {
notifySelectionChanged = callback;
......@@ -115,6 +124,7 @@ describe('Guest', () => {
},
'./integrations': { createIntegration: fakeCreateIntegration },
'./highlighter': highlighter,
'./hypothesis-injector': { HypothesisInjector: FakeHypothesisInjector },
'./range-util': rangeUtil,
'./cross-frame': { CrossFrame },
'./selection-observer': {
......@@ -1159,6 +1169,12 @@ describe('Guest', () => {
);
});
it('stops injecting client into annotation-enabled iframes', () => {
const guest = createGuest();
guest.destroy();
assert.calledWith(fakeHypothesisInjector.destroy);
});
describe('#contentContainer', () => {
it('returns document content container', () => {
const guest = createGuest();
......@@ -1193,4 +1209,17 @@ describe('Guest', () => {
assert.isTrue(guest.sideBySideActive);
});
});
describe('#injectClient', () => {
it('injects client into target frame', async () => {
const config = {};
const guest = createGuest({});
const frame = {};
await guest.injectClient(frame);
assert.calledWith(FakeHypothesisInjector, guest.element, config);
assert.calledWith(fakeHypothesisInjector.injectClient, frame);
});
});
});
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