-
Notifications
You must be signed in to change notification settings - Fork 204
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
Route guest frame unload notifications via host frame #3812
Conversation
Implement the `injectClient` method of Guest that is used by integrations (eg. VitalSource) to inject the client into a chosen frame. In the process the `HypothesisInjector` class has been extracted out of the `CrossFrame` class, since it is unrelated to the rest of the functionality in that class and only lived their because of its dependence on the `Bridge` instance, which will soon be removed (see #3812). It is now constructed and used directly by the `Guest` class instead.
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.
In this PR, there is an implicit assumption that a host
frame is the direct parent of the guest
frame. However, the guest
frame could be a descendant of the host
(child, grand-child, etc..).
I think one way the above issue while still supporting cross-origin guest
frames is to pass a configuration that tells the position from the host
frame:
subframeIdentifier: ....,
hostFrame: 3
which will indicate that the host
frame is three levels up: window.parent.parent.parent
.
@@ -51,15 +51,14 @@ function init() { | |||
window_.__hypothesis = {}; | |||
|
|||
const annotatorConfig = getConfig('annotator'); | |||
const hostFrame = annotatorConfig.subFrameIdentifier ? window.parent : window; |
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.
The host
frame could be more than one level up: window.parent.parent....
. I think you need to look up until you have a window without the subFrameIdentifier
. The problem with that is that only works if the parent window is same-origin.
let sidebar; | ||
if (!annotatorConfig.subFrameIdentifier) { | ||
if (window === hostFrame) { |
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 prefer the previous version than this indirection.
// Notify sidebar when a guest is unloaded. This message is routed via | ||
// the host frame because in Safari guest frames are unable to send messages | ||
// directly to the sidebar during a window's 'unload' event. | ||
// See https://bugs.webkit.org/show_bug.cgi?id=231167. |
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 really annoying bug. Because of that we need to make all these changes.
src/annotator/sidebar.js
Outdated
// directly to the sidebar during a window's 'unload' event. | ||
// See https://bugs.webkit.org/show_bug.cgi?id=231167. | ||
this._listeners.add(window, 'message', event => { | ||
const messageData = /** @type {MessageEvent} */ (event).data; |
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.
const messageData = /** @type {MessageEvent} */ (event).data; | |
const {data} = /** @type {MessageEvent} */ (event); |
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.
It is possible (albeit a bit pointless) that some code in the host page may send a message
event with a data
value that is null
or undefined
. For this reason we don't destructure here.
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 think {data: null}
is a valid MessageEvent
, but data
property is always defined.
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.
Oh, indeed, you're right. I'll make this change then.
You're right there is this assumption. Currently all the use cases we have (ie. ebook readers) only involve guest frames that are direct children of the host. There is also some existing code in I think your proposal to add something to the config to indicate how many levels up the tree the host is makes sense as a future addition, but I'd like to keep the direct-child assumption in this PR. |
This enables testing handling of the client's discovery of existing frames (on page load) as well as handling of dynamic addition and removal after the client has started.
Change how the sidebar is notified of guest frames being unloaded to support guest frames where the client has been loaded via means other than `HypothesisInjector` or where the guest is cross-origin. Instead of listening for the guest frame's 'unload' event from the parent frame in `HypothesisInjector`, the guest frame instead listens for this event itself and sends a `hypothesisGuestUnloaded` message to the host frame via `window.postMessage`, which in turn is handled in the `Sidebar` class to relay it to the sidebar app via a `destroyFrame` RPC call. This indirect route works around a bug in Safari (see code comments). As well as supporting future use cases, this also simplifies the `HypothesisInjector` class as it no longer needs access to the `Bridge`.
189dcfa
to
e6af3ec
Compare
Implement the `injectClient` method of Guest that is used by integrations (eg. VitalSource) to inject the client into a chosen frame. In the process the `HypothesisInjector` class has been extracted out of the `CrossFrame` class, since it is unrelated to the rest of the functionality in that class and only lived their because of its dependence on the `Bridge` instance, which will soon be removed (see #3812). It is now constructed and used directly by the `Guest` class instead.
Implement the `injectClient` method of Guest that is used by integrations (eg. VitalSource) to inject the client into a chosen frame. In the process the `HypothesisInjector` class has been extracted out of the `CrossFrame` class, since it is unrelated to the rest of the functionality in that class and only lived their because of its dependence on the `Bridge` instance, which will soon be removed (see #3812). It is now constructed and used directly by the `Guest` class instead.
Depends on #3807
Change how the sidebar is notified of guest frames being unloaded to
support guest frames where the client has been loaded via means other
than
HypothesisInjector
or where the guest is cross-origin.Instead of listening for the guest frame's 'unload' event from the
parent frame in
HypothesisInjector
, the guest frame instead listensfor this event itself and sends a
hypothesisGuestUnloaded
message tothe host frame via
window.postMessage
, which in turn is handled in theSidebar
class to relay it to the sidebar app via adestroyFrame
RPCcall. This indirect route works around a bug in Safari (see code
comments).
As well as supporting future use cases, this also simplifies the
HypothesisInjector
class as it no longer needs access to theBridge
.To facilitate testing, I changed the http://localhost:3000/document/parent-frame page to add a "Toggle frame" button that removes or adds the iframe to the page.
Testing:
When the frame is removed the annotations should disappear from the sidebar, and when they are re-added, they should re-appear again. They may show up as Orphans when loaded, but this is due to an existing issue unrelated to these changes.
Additionally if you combine this branch with #3811, you'll see the list of URLs in the Help panel's "About this version" tab update to reflect the connected frames when you toggle the presence of the guest frame.