diff --git a/src/annotator/config/index.ts b/src/annotator/config/index.ts index 8ddc75e5c8f..030ec763028 100644 --- a/src/annotator/config/index.ts +++ b/src/annotator/config/index.ts @@ -39,6 +39,7 @@ function configurationKeys(context: Context): string[] { annotator: [ 'clientUrl', 'contentInfoBanner', + 'contentReady', 'subFrameIdentifier', 'sideBySide', ], @@ -120,6 +121,11 @@ const configDefinitions: ConfigDefinitionMap = { defaultValue: null, getValue: getHostPageSetting, }, + contentReady: { + allowInBrowserExt: true, + defaultValue: null, + getValue: getHostPageSetting, + }, enableExperimentalNewNoteButton: { allowInBrowserExt: false, defaultValue: null, diff --git a/src/annotator/config/test/index-test.js b/src/annotator/config/test/index-test.js index f11ba19b3a6..b6432d401bb 100644 --- a/src/annotator/config/test/index-test.js +++ b/src/annotator/config/test/index-test.js @@ -81,6 +81,7 @@ describe('annotator/config/index', () => { bucketContainerSelector: null, clientUrl: 'fakeValue', contentInfoBanner: null, + contentReady: 'fakeValue', enableExperimentalNewNoteButton: null, externalContainerSelector: null, focus: null, @@ -116,6 +117,7 @@ describe('annotator/config/index', () => { bucketContainerSelector: 'fakeValue', clientUrl: 'fakeValue', contentInfoBanner: 'fakeValue', + contentReady: 'fakeValue', enableExperimentalNewNoteButton: 'fakeValue', externalContainerSelector: 'fakeValue', focus: 'fakeValue', @@ -175,6 +177,7 @@ describe('annotator/config/index', () => { bucketContainerSelector: null, clientUrl: null, contentInfoBanner: null, + contentReady: null, enableExperimentalNewNoteButton: null, externalContainerSelector: null, focus: null, @@ -233,6 +236,7 @@ describe('annotator/config/index', () => { expectedKeys: [ 'clientUrl', 'contentInfoBanner', + 'contentReady', 'subFrameIdentifier', 'sideBySide', ], diff --git a/src/annotator/guest.ts b/src/annotator/guest.ts index e7d1b1a6c4f..91b2e9c1e47 100644 --- a/src/annotator/guest.ts +++ b/src/annotator/guest.ts @@ -105,6 +105,12 @@ export type GuestConfig = { /** Configures a banner or other indicators showing where the content has come from. */ contentInfoBanner?: ContentInfoConfig; + /** + * Promise that the guest should wait for before it attempts to anchor + * annotations. + */ + contentReady?: Promise; + sideBySide?: SideBySideOptions; }; @@ -191,6 +197,12 @@ export class Guest extends TinyEmitter implements Annotator, Destroyable { /** Promise that resolves when feature flags are received from the sidebar. */ private _featureFlagsReceived: Promise; + /** + * Promise that the guest will wait for before attempting to anchor + * annotations. + */ + private _contentReady?: Promise; + private _adder: Adder; private _clusterToolbar?: HighlightClusterController; private _hostFrame: Window; @@ -252,6 +264,7 @@ export class Guest extends TinyEmitter implements Annotator, Destroyable { super(); this.element = element; + this._contentReady = config.contentReady; this._hostFrame = hostFrame; this._highlightsVisible = false; this._isAdderVisible = false; @@ -603,6 +616,11 @@ export class Guest extends TinyEmitter implements Annotator, Destroyable { * re-anchoring the annotation. */ async anchor(annotation: AnnotationData): Promise { + if (this._contentReady) { + await this._contentReady; + this._contentReady = undefined; + } + /** * Resolve an annotation's selectors to a concrete range. */ diff --git a/src/annotator/test/guest-test.js b/src/annotator/test/guest-test.js index 644b80483e7..18b4484c1de 100644 --- a/src/annotator/test/guest-test.js +++ b/src/annotator/test/guest-test.js @@ -1336,6 +1336,27 @@ describe('Guest', () => { assert.lengthOf(guest.anchors, 0); }); + + it('waits for content to be ready before anchoring', async () => { + const events = []; + fakeIntegration.anchor = async () => { + events.push('fakeIntegration.anchor'); + return range; + }; + const contentReady = delay(1).then(() => { + events.push('contentReady'); + }); + + const guest = createGuest({ contentReady }); + + const annotation = { + $tag: 'tag1', + target: [{ selector: [{ type: 'TextQuoteSelector', exact: 'hello' }] }], + }; + await guest.anchor(annotation); + + assert.deepEqual(events, ['contentReady', 'fakeIntegration.anchor']); + }); }); describe('#detach', () => {