Commit a7239d05 authored by Robert Knight's avatar Robert Knight

Cleanup and abort if annotator finds existing `<hypothesis-sidebar>`

Fix an issue where multiple Hypothesis sidebar (and possibly notebook, profile
etc.) iframes could get created when saving a local snapshot of a web page where
Hypothesis was loaded. This would lead to one of the sidebars not functioning
because the host frame only allows one sidebar to connect.

Aside from there being multiple sidebars, there are other problems in such an
environment, such as the URL not matching the original page URL.  Hence this
commit handles this scenario by detecting it when the annotator bundle loads,
removing the existing `<hypothesis-sidebar>` element and then aborting startup.

The end result for the user is that if they save a snapshot of a page with
Hypothesis loaded and later reload it, the visible highlights will still appear
in the page but other Hypothesis UI elements will not be visible.

The scenario described here applies in Chrome when saving a snapshot with the
"Web Page, Complete" file type. If the user instead selects "Web Page, HTML
only" or "Web Page, single file" they will get different results, but those
were already not problematic because no JS was executed.

Fixes https://github.com/hypothesis/client/issues/5827
parent 5c3cdd5f
......@@ -34,6 +34,31 @@ const sidebarLinkElement = document.querySelector(
'link[type="application/annotator+html"][rel="sidebar"]',
) as HTMLLinkElement;
/**
* Find and remove existing `<hypothesis-sidebar>` elements, and other
* Hypothesis application containers, which are created in the host frame.
*
* These might exist if the current page is a local snapshot of a web page saved
* with the browser's "Save Page As" feature. In that case the snapshot can
* include both the annotator bundle JS and the DOM elements it created. See
* https://github.com/hypothesis/client/issues/5827.
*
* Having duplicates of these elements is problematic because they contain
* iframed apps which will try to communicate with the host frame, and the
* host frame assumes there is only one of each.
*
* Returns true if any such elements were found.
*/
function removeExistingHypothesisAppElements(): boolean {
const appElements = document.querySelectorAll(
['hypothesis-sidebar', 'hypothesis-notebook', 'hypothesis-profile'].join(
',',
),
);
appElements.forEach(el => el.remove());
return appElements.length > 0;
}
/**
* Entry point for the part of the Hypothesis client that runs in the page being
* annotated.
......@@ -60,6 +85,17 @@ function init() {
const destroyables = [] as Destroyable[];
if (hostFrame === window) {
if (removeExistingHypothesisAppElements()) {
// If there were existing `<hypothesis-sidebar>` etc. elements, we are in
// an "abnormal" environment such as a snapshot of a web page where
// Hypothesis was loaded. We assume we can't function in such an
// environment, so we clean up the previous elements and abort.
console.warn(
'Hypothesis did not load because it found an existing instance on the page.',
);
return;
}
// Ensure port "close" notifications from eg. guest frames are delivered properly.
const removeWorkaround = installPortCloseWorkaroundForSafari();
destroyables.push({ destroy: removeWorkaround });
......
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