-
Notifications
You must be signed in to change notification settings - Fork 198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Explicitly specify guest/sidebar frames to ping in startDiscovery
#3599
Conversation
e27c39b
to
f9bbf67
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hokay! Once one gets one's head around this architecture, it makes general sense. There's still the moderately-steep cognitive investment required for a developer to get up to speed with how these pieces play together; I'm hoping that is something that will be paid down a bit with continued improvement in these areas.
The net effect isn't too complicated, I suppose, which is:
host
frame will create the sidebar frame and stick it in a global reference available to otherframes
(with origin restrictions, etc.)- any additional
guest
frame will look for that global sidebar-window reference and attempt to establish communication with it.
This differs from the past implementation in which the guest frames didn't do anything, but the discovery
service would attempt to iterate through descendant frames and establish connections between them and the (I think?) sidebar. This had a shortcoming because the list of frames available to that discovery service didn't include shadow-DOM-ed frames.
(I know I'm just re-saying what you already said, but I want to be sure I've got it right!)
Given that, the approach seems OK as far as I can tell.
Most of my comments here are around places that commenting could help out in the interim.
src/annotator/cross-frame.js
Outdated
/** | ||
* Attempt to connect to the sidebar frame. | ||
* | ||
* @param {Window} frame |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand this to explain which frame we're talking about? I presume it's a given guest frame that wants to find a sidebar?
@@ -34,32 +34,49 @@ const appLinkEl = /** @type {Element} */ ( | |||
); | |||
|
|||
function init() { | |||
window_.__hypothesis = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be typed/documented?
const eventBus = new EventBus(); | ||
const guest = new Guest(document.body, eventBus, { | ||
...annotatorConfig, | ||
// Load the PDF anchoring/metadata integration. | ||
// nb. documentType is an internal config property only | ||
documentType: isPDF ? 'pdf' : 'html', | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps a comment explaining that the host will create the sidebar, while the guest-only frames (which have a subFrameIdentifier
)...don't. Anything to help reduce the magic-ness of subFrameIdentifier
here would be helpful!
src/annotator/index.js
Outdated
// Clear `annotations` value from the notebook's config to prevent direct-linked | ||
// annotations from filtering the threads. | ||
const notebook = new Notebook(document.body, eventBus, getConfig('notebook')); | ||
|
||
// Setup guest <-> sidebar communication. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Setup guest <-> sidebar communication. | |
// Set up communication between this window/frame (guest) and the sidebar frame |
Or similar?
: /** @type {HypothesisWindow} */ (window.parent).__hypothesis | ||
?.sidebarWindow; | ||
if (sidebarWindow) { | ||
guest.crossframe.connectToSidebar(sidebarWindow); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate that this is a separate step from CrossFrame
object instantiation—it feels right 👍🏻
* Flag used to indicate that the "annotator" part of Hypothesis is loaded in | ||
* the current frame. | ||
* @prop {object} [__hypothesis] - Internal data related to supporting guests in iframes | ||
* @prop {Window} [sidebarWindow] - The sidebar window that is active in this frame. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "this frame" mean, exactly, here?
(source, origin, token) => | ||
this._bridge.createChannel({ source, origin, token }), | ||
[ | ||
// Ping the host frame which is in most cases also the only guest. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you help me understand why it's relevant here that the host frame is usually the only guest? Is it assured that the parent frame is definitely the host?
|
||
// Ping specified other frames to tell them about the existence of this frame. | ||
const beaconMessage = this.server | ||
? '__cross_frame_dhcp_offer' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to tell me to take a hike because this couldn't be more tangential, but I'm curious as to the meaning of dhcp
in this context. Is it meaningful? DCHP as I (don't really) understand it seems pretty specific. Again, just a curiosity question.
f9bbf67
to
eece43e
Compare
Codecov Report
@@ Coverage Diff @@
## master #3599 +/- ##
==========================================
+ Coverage 98.60% 98.61% +0.01%
==========================================
Files 211 211
Lines 7738 7735 -3
Branches 1758 1758
==========================================
- Hits 7630 7628 -2
+ Misses 108 107 -1
Continue to review full report at Codecov.
|
I have updated this PR to add tests and also some additional documentation per PR feedback. |
The logic used by guest/host and sidebar frames to find each other in `Discovery#startDiscovery` relied on traversing the frame tree starting from `window.top` using `window.frames`. Since `window.frames` doesn't include frames in shadow roots, this did not work if _both_ the guest and sidebar are contained within a shadow root. The sidebar is always contained in a shadow root created by the `<hypothesis-sidebar>` element, so this simplifies to only working if the guest/host is not contained in a shadow root. For current use cases it is possible for guest frames to get a direct reference to the sidebar frame they should be communicating with and for the sidebar to get direct a reference to the host frame (its parent). This avoids the need for `window.frames` traversal. This commit leverages that by changing `startDiscovery` to take an explicit array of frames to ping. Guest frames invoke `startDiscovery` passing a reference to the sidebar frame and sidebar frames invoke `startDiscovery` passing a reference to the parent (host) frame. This fixes the following scenarios: - Hypothesis not completing loading if host frame is contained within a shadow root. eg. In the VitalSource book reader. - Same-origin guest frames loading Hypothesis after the sidebar has already loaded. See http://localhost:3000/document/parent-frame test case in client dev server. - Multiple sidebars attempting to connect to the same guest frame, if Hypothesis is loaded multiple times in different parts of the frame tree. See http://localhost:3000/document/multi-frames test case in client dev server. This approach has some limitations: - Child guest frames which are not same-origin with the parent host frame are not supported. - Child guest frames which attempt to connect to the sidebar before it has loaded will fail to connect. This could be remedied by making the guest wait for confirmation of the sidebar having loaded before calling `connectToSidebar`. This doesn't affect the host frame since the sidebar pings that frame directly. These limitations are acceptable in the short term but will be remedied by a larger overhaul of inter-frame communication that is currently being worked on.
Add comments to clarify various aspects of the way the host/guest frames are set up and interact.
Rather than use a dedicated getter to expose the sidebar frame, it seemed better on a second look to use the existing `sidebar.iframe` property that exposes the `<iframe>` element, but document and test that property.
eece43e
to
0f70af2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have verified locally that these changes cause the "manual test case" document in the dev server to work as expected.
Unrelated to these changes, the FrameSync
test fixtures, fakes and setup are complex enough that I don't feel confident in being able to guarantee the "whole picture," but the changes you made within them make sense. (My point here is that the pre-existing tests have a bit of a complexity-smell; nothing to address right now).
fakeDiscovery = { | ||
startDiscovery: sinon.stub(), | ||
}; | ||
const FakeDiscovery = sinon.stub().returns(fakeDiscovery); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. This is more consistent with our test conventions.
}); | ||
|
||
afterEach(() => { | ||
$imports.$restore(); | ||
}); | ||
|
||
describe('#connect', () => { | ||
it('establishes a connection to the parent host/guest frame', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is splitting hairs, but it doesn't have any knowledge about the host-guest-iness of the parent frame; it's merely establishing a connection with whatever the parent frame is.
The logic used by guest/host and sidebar frames to find each other in
Discovery#startDiscovery
relied on traversing the frame tree startingfrom
window.top
usingwindow.frames
. Sincewindow.frames
doesn'tinclude frames in shadow roots, this did not work if both the guest and
sidebar are contained within a shadow root. The sidebar is always
contained in a shadow root created by the
<hypothesis-sidebar>
element, so this simplifies to only working if the guest/host is not
contained in a shadow root.
For current use cases it is possible for guest frames to get a direct
reference to the sidebar frame they should be communicating with and for
the sidebar to get direct a reference to the host frame (its parent).
This avoids the need for
window.frames
traversal.This commit leverages that by changing
startDiscovery
to take anexplicit array of frames to ping. Guest frames invoke
startDiscovery
passing a reference to the sidebar frame and sidebar frames invoke
startDiscovery
passing a reference to the parent (host) frame.This fixes the following scenarios:
Hypothesis not completing loading if host frame is contained within a
shadow root. eg. In the VitalSource book reader. See http://localhost:3000/document/host-in-shadow-root test case in client dev server.
Same-origin guest frames loading Hypothesis after the sidebar has
already loaded. See http://localhost:3000/document/parent-frame test
case in client dev server.
Multiple sidebars attempting to connect to the same guest frame, if
Hypothesis is loaded multiple times in different parts of the frame
tree. See http://localhost:3000/document/multi-frames test case in
client dev server.
This approach has some limitations:
are not supported.
has loaded will fail to connect. This could be remedied by making the
guest wait for confirmation of the sidebar having loaded before
calling
connectToSidebar
. This doesn't affect the host frame sincethe sidebar pings that frame directly.
These limitations are acceptable in the short term but will be remedied
by a larger overhaul of inter-frame communication that is currently
being worked on.
Fixes #3590
This is also a short-term fix for #249 and #187. A better solution will be coming with #3533.
Discovery
,CrossFrame
,FrameSync