Commit c0badf5f authored by Robert Knight's avatar Robert Knight

Send annotation to first frame when there is no URI match or main frame

Handle the case when an annotation is returned from the backend whose URL does
not match the frame URL and where there is no "main" guest frame. This can
happen in VitalSource, where no guest is created in the host frame.

The general solution to this is to change the client and h so that it can always
determine which query URL an annotation from h matched. See [1]. This is an
interim step which will work in VitalSource and similar scenarios.

[1] https://github.com/hypothesis/client/issues/4191
parent d19bac71
...@@ -37,21 +37,26 @@ export function formatAnnot({ $tag, target, uri }) { ...@@ -37,21 +37,26 @@ export function formatAnnot({ $tag, target, uri }) {
/** /**
* Return the frame which best matches an annotation. * Return the frame which best matches an annotation.
* *
* If there is a frame whose URL exactly matches the annotation, it will be
* returned. Otherwise the main frame will be returned. Currently this may fail
* to return the expected frame if an annotation was fetched from h that
* does not match one of the frame URIs. This can happen due to URI expansion /
* document equivalence in h.
*
* @param {Frame[]} frames * @param {Frame[]} frames
* @param {Annotation} ann * @param {Annotation} ann
*/ */
function frameForAnnotation(frames, ann) { function frameForAnnotation(frames, ann) {
const frame = frames.find(f => f.uri === ann.uri); // Choose frame with an exact URL match if possible. In the unlikely situation
if (frame) { // where multiple frames have the same URL, we'll use whichever connected first.
return frame; const uriMatch = frames.find(f => f.uri === ann.uri);
if (uriMatch) {
return uriMatch;
} }
return frames.find(f => f.id === null);
// If there is no exact URL match, choose the main/host frame for consistent results.
const mainFrame = frames.find(f => f.id === null);
if (mainFrame) {
return mainFrame;
}
// If there is no main frame (eg. in VitalSource), fall back to whichever
// frame connected first.
return frames[0];
} }
/** /**
......
...@@ -277,6 +277,40 @@ describe('FrameSyncService', () => { ...@@ -277,6 +277,40 @@ describe('FrameSyncService', () => {
); );
}); });
it('sends annotation to first frame if there is no frame with matching URL or main frame', async () => {
const annotation = {
id: 'abc',
uri: 'urn:book-id:1234',
};
const frames = [];
fakeStore.connectFrame.callsFake(frame => {
frames.push({ id: frame.id, uri: frame.uri });
});
fakeStore.frames.returns(frames);
// Connect a single guest which is not the main/host frame. This simulates
// what happens in VitalSource for example.
await connectGuest();
emitGuestEvent('documentInfoChanged', {
frameIdentifier: 'iframe',
// Note that URI does not match annotation URI. The backend can still return
// the annotation for this frame based on URI equivalence information.
uri: 'https://publisher.com/books/1234/chapter1.html',
});
fakeStore.setState({
annotations: [annotation],
});
assert.calledWithMatch(
guestRPC().call,
'loadAnnotations',
sinon.match([formatAnnot(annotation)])
);
});
it('sends a "loadAnnotations" message only for new annotations', async () => { it('sends a "loadAnnotations" message only for new annotations', async () => {
const frameInfo = fixtures.htmlDocumentInfo; const frameInfo = fixtures.htmlDocumentInfo;
await connectGuest(); await connectGuest();
......
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