Commit 55f2ec61 authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Fix create of a page not in VitalSource

After #4176, the 'create note' button has stopped working in
VitalSource. That's because we don't create the guest frame in the host
frame. Creating a note relayed on one of the guest frames having a
frameIdentifier` with value `null`. That's not longer the case.

Now, when creating a page note we send the request to the first
connected guest frame. This is analogous to #4177.

In addition, we only send `textUnselected`, `textSelected` and
`createAnnotation` RPC events to only specific guest frames. That
simplifies the logic by avoiding to send a `frameIdentifier`.
parent dc33e4be
...@@ -302,28 +302,12 @@ export class Guest { ...@@ -302,28 +302,12 @@ export class Guest {
} }
async _connectHost() { async _connectHost() {
this._hostRPC.on( this._hostRPC.on('clearSelection', () => {
'clearSelectionExceptIn',
/** @param {string|null} frameIdentifier */
frameIdentifier => {
if (this._frameIdentifier === frameIdentifier) {
return;
}
this._informHostOnNextSelectionClear = false; this._informHostOnNextSelectionClear = false;
removeTextSelection(); removeTextSelection();
} });
);
this._hostRPC.on( this._hostRPC.on('createAnnotation', () => this.createAnnotation());
'createAnnotationIn',
/** @param {string|null} frameIdentifier */
frameIdentifier => {
if (this._frameIdentifier === frameIdentifier) {
this.createAnnotation();
}
}
);
this._hostRPC.on( this._hostRPC.on(
'focusAnnotations', 'focusAnnotations',
...@@ -692,7 +676,7 @@ export class Guest { ...@@ -692,7 +676,7 @@ export class Guest {
} }
this.selectedRanges = [range]; this.selectedRanges = [range];
this._hostRPC.call('textSelectedIn', this._frameIdentifier); this._hostRPC.call('textSelected');
this._adder.annotationsForSelection = annotationsForSelection(); this._adder.annotationsForSelection = annotationsForSelection();
this._isAdderVisible = true; this._isAdderVisible = true;
...@@ -704,7 +688,7 @@ export class Guest { ...@@ -704,7 +688,7 @@ export class Guest {
this._adder.hide(); this._adder.hide();
this.selectedRanges = []; this.selectedRanges = [];
if (this._informHostOnNextSelectionClear) { if (this._informHostOnNextSelectionClear) {
this._hostRPC.call('textUnselectedIn', this._frameIdentifier); this._hostRPC.call('textUnselected');
} }
this._informHostOnNextSelectionClear = true; this._informHostOnNextSelectionClear = true;
} }
......
...@@ -67,10 +67,10 @@ export class Sidebar { ...@@ -67,10 +67,10 @@ export class Sidebar {
this._emitter = eventBus.createEmitter(); this._emitter = eventBus.createEmitter();
/** /**
* Tracks which `Guest` has a text selection. `null` indicates the host frame, * Tracks which `Guest` has a text selection. `null` indicates to default
* the top-level frame. Other `guest` frames use string identifier. * to the first connected guest frame.
* *
* @type {string|null} * @type {PortRPC|null}
*/ */
this._guestWithSelection = null; this._guestWithSelection = null;
...@@ -145,10 +145,14 @@ export class Sidebar { ...@@ -145,10 +145,14 @@ export class Sidebar {
// Set up the toolbar on the left edge of the sidebar. // Set up the toolbar on the left edge of the sidebar.
const toolbarContainer = document.createElement('div'); const toolbarContainer = document.createElement('div');
this.toolbar = new ToolbarController(toolbarContainer, { this.toolbar = new ToolbarController(toolbarContainer, {
createAnnotation: () => createAnnotation: () => {
this._guestRPC.forEach(rpc => if (this._guestRPC.length === 0) {
rpc.call('createAnnotationIn', this._guestWithSelection) return;
), }
const rpc = this._guestWithSelection ?? this._guestRPC[0];
rpc.call('createAnnotation');
},
setSidebarOpen: open => (open ? this.open() : this.close()), setSidebarOpen: open => (open ? this.open() : this.close()),
setHighlightsVisible: show => this.setHighlightsVisible(show), setHighlightsVisible: show => this.setHighlightsVisible(show),
}); });
...@@ -261,29 +265,21 @@ export class Sidebar { ...@@ -261,29 +265,21 @@ export class Sidebar {
/** @type {PortRPC<GuestToHostEvent, HostToGuestEvent>} */ /** @type {PortRPC<GuestToHostEvent, HostToGuestEvent>} */
const guestRPC = new PortRPC(); const guestRPC = new PortRPC();
guestRPC.on( guestRPC.on('textSelected', () => {
'textSelectedIn', this._guestWithSelection = guestRPC;
/** @param {string|null} frameIdentifier */
frameIdentifier => {
this._guestWithSelection = frameIdentifier;
this.toolbar.newAnnotationType = 'annotation'; this.toolbar.newAnnotationType = 'annotation';
this._guestRPC.forEach(rpc => this._guestRPC
rpc.call('clearSelectionExceptIn', frameIdentifier) .filter(port => port !== guestRPC)
); .forEach(rpc => rpc.call('clearSelection'));
} });
);
guestRPC.on( guestRPC.on('textUnselected', () => {
'textUnselectedIn', this._guestWithSelection = null;
/** @param {string|null} frameIdentifier */
frameIdentifier => {
this._guestWithSelection = null; // default to the `host` frame
this.toolbar.newAnnotationType = 'note'; this.toolbar.newAnnotationType = 'note';
this._guestRPC.forEach(rpc => this._guestRPC
rpc.call('clearSelectionExceptIn', frameIdentifier) .filter(port => port !== guestRPC)
); .forEach(rpc => rpc.call('clearSelection'));
} });
);
// The listener will do nothing if the sidebar doesn't have a bucket bar // The listener will do nothing if the sidebar doesn't have a bucket bar
// (clean theme) // (clean theme)
...@@ -303,6 +299,9 @@ export class Sidebar { ...@@ -303,6 +299,9 @@ export class Sidebar {
guestRPC.on('close', () => { guestRPC.on('close', () => {
guestRPC.destroy(); guestRPC.destroy();
if (guestRPC === this._guestWithSelection) {
this._guestWithSelection = null;
}
this._guestRPC = this._guestRPC.filter(rpc => rpc !== guestRPC); this._guestRPC = this._guestRPC.filter(rpc => rpc !== guestRPC);
}); });
......
...@@ -439,6 +439,17 @@ describe('Guest', () => { ...@@ -439,6 +439,17 @@ describe('Guest', () => {
}); });
}); });
describe('on "createAnnotation" event', () => {
it('creates an annotation', async () => {
createGuest();
emitHostEvent('createAnnotation');
await delay(0);
assert.calledWith(sidebarRPC().call, 'createAnnotation');
});
});
describe('on "deleteAnnotation" event', () => { describe('on "deleteAnnotation" event', () => {
it('detaches annotation', () => { it('detaches annotation', () => {
createGuest(); createGuest();
...@@ -645,38 +656,20 @@ describe('Guest', () => { ...@@ -645,38 +656,20 @@ describe('Guest', () => {
assert.notCalled(FakeAdder.instance.show); assert.notCalled(FakeAdder.instance.show);
}); });
it('calls "textSelectedIn" RPC method with argument "null" if selection is non-empty', () => { it('calls "textSelected" RPC method when selecting text', () => {
createGuest(); createGuest();
simulateSelectionWithText(); simulateSelectionWithText();
assert.calledWith(hostRPC().call, 'textSelectedIn', null); assert.calledWith(hostRPC().call, 'textSelected');
}); });
it('calls "textSelectedIn" RPC method with the subFrameIdentifier as argument if selection is non-empty', () => { it('calls "textUnselected" RPC method when clearing text selection', () => {
const subFrameIdentifier = 'subframe identifier';
createGuest({ subFrameIdentifier });
simulateSelectionWithText();
assert.calledWith(hostRPC().call, 'textSelectedIn', subFrameIdentifier);
});
it('calls "textUnselectedIn" RPC method with argument "null" if selection is empty', () => {
createGuest(); createGuest();
simulateSelectionWithoutText(); simulateSelectionWithoutText();
assert.calledWith(hostRPC().call, 'textUnselectedIn', null); assert.calledWith(hostRPC().call, 'textUnselected');
});
it('calls "textUnselectedIn" RPC method with the subFrameIdentifier as argument if selection is empty', () => {
const subFrameIdentifier = 'subframe identifier';
createGuest({ subFrameIdentifier });
simulateSelectionWithoutText();
assert.calledWith(hostRPC().call, 'textUnselectedIn', subFrameIdentifier);
}); });
it('unselects text if another iframe has made a selection', () => { it('unselects text if another iframe has made a selection', () => {
...@@ -687,7 +680,7 @@ describe('Guest', () => { ...@@ -687,7 +680,7 @@ describe('Guest', () => {
simulateSelectionWithText(); simulateSelectionWithText();
hostRPC().call.resetHistory(); hostRPC().call.resetHistory();
emitHostEvent('clearSelectionExceptIn', 'subframe identifier'); emitHostEvent('clearSelection');
assert.calledOnce(removeAllRanges); assert.calledOnce(removeAllRanges);
notifySelectionChanged(null); // removing the text selection triggers the selection observer notifySelectionChanged(null); // removing the text selection triggers the selection observer
...@@ -698,7 +691,7 @@ describe('Guest', () => { ...@@ -698,7 +691,7 @@ describe('Guest', () => {
// On next selection clear it should be inform the host. // On next selection clear it should be inform the host.
notifySelectionChanged(null); notifySelectionChanged(null);
assert.calledOnce(hostRPC().call); assert.calledOnce(hostRPC().call);
assert.calledWithExactly(hostRPC().call, 'textUnselectedIn', null); assert.calledWithExactly(hostRPC().call, 'textUnselected');
}); });
it("doesn't unselect text if frame identifier matches", () => { it("doesn't unselect text if frame identifier matches", () => {
...@@ -809,22 +802,6 @@ describe('Guest', () => { ...@@ -809,22 +802,6 @@ describe('Guest', () => {
}); });
describe('#createAnnotation', () => { describe('#createAnnotation', () => {
it('creates an annotation if host calls "createAnnotationIn" RPC method', async () => {
createGuest();
await delay(0);
sidebarRPC().call.resetHistory(); // Discard `documentInfoChanged` call
emitHostEvent('createAnnotationIn', 'dummy');
await delay(0);
assert.notCalled(sidebarRPC().call);
emitHostEvent('createAnnotationIn', null);
await delay(0);
assert.calledWith(sidebarRPC().call, 'createAnnotation');
});
it('adds document metadata to the annotation', async () => { it('adds document metadata to the annotation', async () => {
const guest = createGuest(); const guest = createGuest();
......
...@@ -43,13 +43,13 @@ describe('Sidebar', () => { ...@@ -43,13 +43,13 @@ describe('Sidebar', () => {
}; };
/** Return the PortRPC instance for the first connected guest. */ /** Return the PortRPC instance for the first connected guest. */
const guestRPC = () => { const guestRPC = (index = 1) => {
return fakePortRPCs[1]; return fakePortRPCs[index];
}; };
const emitGuestEvent = (event, ...args) => { const emitNthGuestEvent = (index = 1, event, ...args) => {
const result = []; const result = [];
for (let [evt, fn] of guestRPC().on.args) { for (let [evt, fn] of guestRPC(index).on.args) {
if (event === evt) { if (event === evt) {
result.push(fn(...args)); result.push(fn(...args));
} }
...@@ -57,6 +57,10 @@ describe('Sidebar', () => { ...@@ -57,6 +57,10 @@ describe('Sidebar', () => {
return result; return result;
}; };
const emitGuestEvent = (event, ...args) => {
return emitNthGuestEvent(1, event, ...args);
};
const emitSidebarEvent = (event, ...args) => { const emitSidebarEvent = (event, ...args) => {
const result = []; const result = [];
for (let [evt, fn] of sidebarRPC().on.args) { for (let [evt, fn] of sidebarRPC().on.args) {
...@@ -253,55 +257,70 @@ describe('Sidebar', () => { ...@@ -253,55 +257,70 @@ describe('Sidebar', () => {
assert.calledWith(sidebar.setHighlightsVisible, false); assert.calledWith(sidebar.setHighlightsVisible, false);
}); });
it('creates an annotation in the host frame when toolbar button is clicked', () => { it("doesn't throw when creating an annotation by clicking in toolbar button and there are not connected guests", () => {
createSidebar();
FakeToolbarController.args[0][1].createAnnotation();
});
it('creates an annotation in the first connected guest when toolbar button is clicked', () => {
const sidebar = createSidebar(); const sidebar = createSidebar();
connectGuest(sidebar); connectGuest(sidebar);
FakeToolbarController.args[0][1].createAnnotation(); FakeToolbarController.args[0][1].createAnnotation();
assert.calledWith(guestRPC().call, 'createAnnotationIn', null); assert.calledWith(guestRPC().call, 'createAnnotation');
}); });
it('creates an annotation in frame with selection when toolbar button is clicked', () => { it('creates an annotation in guest with selected text when toolbar button is clicked', () => {
const sidebar = createSidebar({}); const sidebar = createSidebar({});
connectGuest(sidebar); connectGuest(sidebar);
connectGuest(sidebar);
emitNthGuestEvent(2, 'textSelected');
FakeToolbarController.args[0][1].createAnnotation();
assert.neverCalledWith(guestRPC(1).call, 'createAnnotation');
assert.calledWith(guestRPC(2).call, 'createAnnotation');
});
const frameIdentifier = 'subframe identifier'; it('creates an annotation in the first connected guest if guest with selected text has closed', () => {
emitGuestEvent('textSelectedIn', frameIdentifier); const sidebar = createSidebar({});
connectGuest(sidebar);
connectGuest(sidebar);
emitNthGuestEvent(2, 'textSelected');
emitNthGuestEvent(2, 'close');
FakeToolbarController.args[0][1].createAnnotation(); FakeToolbarController.args[0][1].createAnnotation();
assert.calledWith(guestRPC().call, 'createAnnotationIn', frameIdentifier); assert.calledWith(guestRPC(1).call, 'createAnnotation');
assert.neverCalledWith(guestRPC(2).call, 'createAnnotation');
}); });
it('toggles create annotation button to "Annotation" when selection becomes non-empty', () => { it('toggles create annotation button to "Annotation" when selection becomes non-empty', () => {
const sidebar = createSidebar(); const sidebar = createSidebar();
connectGuest(sidebar); connectGuest(sidebar);
connectGuest(sidebar);
const frameIdentifier = 'subframe identifier'; emitGuestEvent('textSelected');
emitGuestEvent('textSelectedIn', frameIdentifier);
assert.equal(sidebar.toolbar.newAnnotationType, 'annotation'); assert.equal(sidebar.toolbar.newAnnotationType, 'annotation');
assert.calledWith( assert.neverCalledWith(guestRPC(1).call, 'clearSelection');
guestRPC().call, assert.calledWith(guestRPC(2).call, 'clearSelection');
'clearSelectionExceptIn',
frameIdentifier
);
}); });
it('toggles create annotation button to "Page Note" when selection becomes empty', () => { it('toggles create annotation button to "Page Note" when selection becomes empty', () => {
const sidebar = createSidebar(); const sidebar = createSidebar();
connectGuest(sidebar); connectGuest(sidebar);
connectGuest(sidebar);
emitGuestEvent('textSelected');
assert.equal(sidebar.toolbar.newAnnotationType, 'annotation');
const frameIdentifier = null; emitGuestEvent('textUnselected');
emitGuestEvent('textUnselectedIn', frameIdentifier);
assert.equal(sidebar.toolbar.newAnnotationType, 'note'); assert.equal(sidebar.toolbar.newAnnotationType, 'note');
assert.calledWith( assert.neverCalledWith(guestRPC(1).call, 'clearSelection');
guestRPC().call, assert.calledWith(guestRPC(2).call, 'clearSelection');
'clearSelectionExceptIn',
frameIdentifier
);
}); });
}); });
......
...@@ -7,15 +7,11 @@ ...@@ -7,15 +7,11 @@
* Events that the guest sends to the host * Events that the guest sends to the host
*/ */
export type GuestToHostEvent = export type GuestToHostEvent =
/** /** The guest informs the host that text has been unselected. */
* The guest informs the host that text has been unselected at a frame with that identifier. | 'textUnselected'
*/
| 'textUnselectedIn'
/** /** The guest informs the host that text has been selected. */
* The guest informs the host that text has been selected at a frame with that identifier. | 'textSelected'
*/
| 'textSelectedIn'
/** /**
* The guest informs the host that the anchors have been changed in the main annotatable frame. * The guest informs the host that the anchors have been changed in the main annotatable frame.
...@@ -68,15 +64,11 @@ export type GuestToSidebarEvent = ...@@ -68,15 +64,11 @@ export type GuestToSidebarEvent =
* Events that the host sends to the guest * Events that the host sends to the guest
*/ */
export type HostToGuestEvent = export type HostToGuestEvent =
/** /** The host requests a guest to create an annotation. */
* The host requests a guest with a specific frame identifier to create an annotation. | 'createAnnotation'
*/
| 'createAnnotationIn'
/** /** The host informs guests that text should be unselected. */
* The host informs guests that text should be unselected, except in the guest with a given frame identifier | 'clearSelection'
*/
| 'clearSelectionExceptIn'
/** /**
* The host informs guests to focus on a set of annotations * The host informs guests to focus on a set of annotations
......
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