Commit f4a8a351 authored by Robert Knight's avatar Robert Knight

Wait for feature flag state before sending document info in VitalSource

In order to facilitate a seamless transition after a chapter navigation in
VitalSource, it will be necessary for the Guest in the new frame to report
document information based on the same `book_as_single_document` feature flag
state as for the previous frame.

Allow integrations to signal whether the Guest should wait to receive feature
flags from the sidebar before sending document info via `documentInfoChanged`,
and enable this for VitalSource. We don't enable this for all integrations
because it adds latency on the critical path to fetching and displaying
annotations.
parent 10e81814
......@@ -135,6 +135,9 @@ export class Guest implements Annotator, Destroyable {
public features: FeatureFlags;
/** Promise that resolves when feature flags are received from the sidebar. */
private _featureFlagsReceived: Promise<void>;
private _adder: Adder;
private _clusterToolbar?: HighlightClusterController;
private _hostFrame: Window;
......@@ -231,12 +234,12 @@ export class Guest implements Annotator, Destroyable {
});
this.features = new FeatureFlags();
this._featureFlagsReceived = new Promise(resolve => {
this.features.on('flagsChanged', resolve);
});
this._integration = createIntegration(this);
this._integration.on('uriChanged', async () => {
const metadata = await this.getDocumentInfo();
this._sidebarRPC.call('documentInfoChanged', metadata);
});
this._integration.on('uriChanged', () => this._sendDocumentInfo());
if (config.contentInfoBanner) {
this._integration.showContentInfo?.(config.contentInfoBanner);
}
......@@ -344,6 +347,15 @@ export class Guest implements Annotator, Destroyable {
};
}
/** Send the current document URI and metadata to the sidebar. */
async _sendDocumentInfo() {
if (this._integration.waitForFeatureFlags?.()) {
await this._featureFlagsReceived;
}
const metadata = await this.getDocumentInfo();
this._sidebarRPC.call('documentInfoChanged', metadata);
}
/**
* Shift the position of the adder on window 'resize' events
*/
......@@ -456,9 +468,8 @@ export class Guest implements Annotator, Destroyable {
this._portFinder.discover('sidebar').then(port => {
this._sidebarRPC.connect(port);
});
this.getDocumentInfo().then(metadata =>
this._sidebarRPC.call('documentInfoChanged', metadata)
);
this._sendDocumentInfo();
}
destroy() {
......
......@@ -272,7 +272,12 @@ describe('annotator/integrations/vitalsource', () => {
it('allows annotation', () => {
const integration = createIntegration();
assert.equal(integration.canAnnotate(), true);
assert.isTrue(integration.canAnnotate());
});
it('asks guest to wait for feature flags before sending document info', () => {
const integration = createIntegration();
assert.isTrue(integration.waitForFeatureFlags());
});
it('delegates to HTMLIntegration for side-by-side mode', () => {
......
......@@ -582,4 +582,15 @@ export class VitalSourceContentIntegration
_bookIsSingleDocument(): boolean {
return this._features.flagEnabled('book_as_single_document');
}
waitForFeatureFlags() {
// The `book_as_single_document` flag changes the URI reported by this
// integration.
//
// Ask the guest to delay reporting document metadata to the sidebar until
// feature flags have been received. This ensures that the initial document
// info reported to the sidebar after a chapter navigation is consistent
// between the previous/new guest frames.
return true;
}
}
......@@ -1433,6 +1433,21 @@ describe('Guest', () => {
});
});
it('waits for feature flags before sending metadata if requested by integration', async () => {
fakeIntegration.waitForFeatureFlags = () => true;
createGuest();
await delay(0);
assert.isFalse(sidebarRPC().call.calledWith('documentInfoChanged'));
emitSidebarEvent('featureFlagsUpdated', {
book_as_single_document: true,
});
await delay(0);
assert.isTrue(sidebarRPC().call.calledWith('documentInfoChanged'));
});
it('sends segment info to sidebar when available', async () => {
fakeIntegration.uri.resolves('https://bookstore.com/books/1234');
fakeIntegration.getMetadata.resolves({ title: 'A little book' });
......
......@@ -221,6 +221,12 @@ export type IntegrationBase = {
scrollToAnchor(a: Anchor): Promise<void>;
/** Show information about the current document and content provider */
showContentInfo?(config: ContentInfoConfig): void;
/**
* Whether the Guest should wait for feature flags to be received from the
* sidebar before sending initial document info to the sidebar.
*/
waitForFeatureFlags?(): boolean;
};
export type Integration = Destroyable & TinyEmitter & IntegrationBase;
......
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