Commit ff5918d6 authored by Robert Knight's avatar Robert Knight

Move notebook container into shadow DOM

Follow the example of the `<hypothesis-sidebar>` element by making the
`<hypothesis-notebook>` element an unstyled shadow-host which serves as
the root container for the notebook, isolating it from the page's
styles. Inside this is a styled element which serves as the visual
container.

As well as isolating the notebook from the host page's styles, this will
also make it possible to avoid loading annotator styles into the host
page. See https://github.com/hypothesis/client/issues/2979.

To reduce resource usage a little when the notebook is not used, only
the notebook's shadow host is created initially. The styled inner
container is created when the notebook is shown for the first time.
parent 66cf493a
import Delegator from './delegator';
import { createSidebarConfig } from './config/sidebar';
import { createShadowRoot } from './util/shadow-root';
/**
* Create the iframe that will load the notebook application.
......@@ -34,9 +35,21 @@ export default class Notebook extends Delegator {
this._groupId = null;
this._prevGroupId = null;
this.container = document.createElement('hypothesis-notebook');
this.container.style.display = 'none';
this.container.className = 'notebook-outer';
/**
* Un-styled shadow host for the notebook content.
*
* This isolates the notebook from the page's styles.
*/
this._outerContainer = document.createElement('hypothesis-notebook');
this.element.appendChild(this._outerContainer);
/**
* Lazily-initialized container for the notebook iframe. This is only created
* when the notebook is actually used.
*
* @type {HTMLElement|null}
*/
this.container = null;
this.subscribe('showNotebook', groupId => {
this._groupId = groupId;
......@@ -48,6 +61,8 @@ export default class Notebook extends Delegator {
}
_update() {
const container = this._initContainer();
// Create a new iFrame if we don't have one at all yet, or if the
// groupId has changed since last use
const needIframe =
......@@ -57,23 +72,39 @@ export default class Notebook extends Delegator {
if (needIframe) {
this.frame?.remove();
this.frame = createNotebookFrame(this.options, this._groupId);
this.container.appendChild(this.frame);
this.element.appendChild(this.container);
container.appendChild(this.frame);
}
}
show() {
const container = this._initContainer();
this._update();
this.container.classList.add('is-open');
this.container.style.display = '';
container.classList.add('is-open');
container.style.display = '';
}
hide() {
this.container.classList.remove('is-open');
this.container.style.display = 'none';
if (this.container) {
this.container.classList.remove('is-open');
this.container.style.display = 'none';
}
}
destroy() {
this.frame?.remove();
this._outerContainer.remove();
}
_initContainer() {
if (this.container) {
return this.container;
}
const shadowRoot = createShadowRoot(this._outerContainer);
this.container = document.createElement('div');
this.container.style.display = 'none';
this.container.className = 'notebook-outer';
shadowRoot.appendChild(this.container);
return this.container;
}
}
......@@ -23,10 +23,18 @@ describe('Notebook', () => {
});
describe('notebook container frame', () => {
it('starts hidden', () => {
it('is not created until the notebook is shown', () => {
const notebook = createNotebook();
assert.isNull(notebook.container);
assert.equal(notebook.container.style.display, 'none');
notebook.show();
assert.isNotNull(notebook.container);
});
it('is not created if `hide` is called before notebook is first shown', () => {
const notebook = createNotebook();
notebook.hide();
assert.isNull(notebook.container);
});
it('displays when opened', () => {
......@@ -137,12 +145,15 @@ describe('Notebook', () => {
describe('destruction', () => {
it('should remove the frame', () => {
const notebook = createNotebook();
const hostDocument = notebook.element;
// Make sure the frame is created
notebook.show();
assert.isNotNull(hostDocument.querySelector('hypothesis-notebook'));
notebook.destroy();
assert.equal(notebook.frame.parentElement, null);
assert.isNull(hostDocument.querySelector('hypothesis-notebook'));
});
});
});
......@@ -2,7 +2,6 @@
@use '../mixins/molecules';
.notebook-outer {
// TODO: CSS namespace conflicts?
box-sizing: border-box;
position: fixed;
top: 0;
......
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