blob: c7f2109c476db99a45e036a57bd28c739a7f22ba [file] [log] [blame]
/** Root element for all codesize.skia.org pages. */
import { html, TemplateResult } from 'lit-html';
import { define } from 'elements-sk/define';
import { SpinnerSk } from 'elements-sk/spinner-sk/spinner-sk';
import { errorMessage } from 'elements-sk/errorMessage';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import '../../../infra-sk/modules/app-sk';
import '../../../infra-sk/modules/theme-chooser-sk';
import 'elements-sk/error-toast-sk';
import 'elements-sk/spinner-sk';
import { END_BUSY_EVENT } from './events';
/** Moves the elements in a NodeList or HTMLCollection as children of another element. */
function move(from: HTMLCollection | NodeList, to: HTMLElement) {
Array.prototype.slice.call(from).forEach((ele) => to.appendChild(ele));
}
export class CodesizeScaffoldSk extends ElementSk {
private static template = (): TemplateResult => html`
<app-sk>
<header>
<h1>Skia Code Size</h1>
<spinner-sk></spinner-sk>
<div class="spacer"></div>
<theme-chooser-sk></theme-chooser-sk>
</header>
<aside>
<ul>
<li><a href="/">Home</a></li>
</ul>
</aside>
<main></main>
<error-toast-sk></error-toast-sk>
</app-sk>
`;
private main: HTMLElement | null = null;
private busyTaskCount = 0;
private spinner: SpinnerSk | null = null;
constructor() {
super(CodesizeScaffoldSk.template);
}
connectedCallback(): void {
super.connectedCallback();
// Don't call more than once.
if (this.main) {
return;
}
// We aren't using shadow dom so we need to manually move the children of codesize-scaffold-sk
// to be children of 'main'. We have to do this for the existing elements and for all future
// mutations.
// Create a temporary holding spot for the elements we're moving.
const div = document.createElement('div');
move(this.children, div);
// Now that we've moved all the old children out of the way we can render the template.
this._render();
// Move the old children back under main.
this.main = this.querySelector('main');
move(div.children, this.main!);
// Move all future children under main also.
const observer = new MutationObserver((mutList) => {
mutList.forEach((mut) => {
move(mut.addedNodes, this.main!);
});
});
observer.observe(this, { childList: true });
this.spinner = this.querySelector<SpinnerSk>('spinner-sk');
// Any unhandled errors will be reported in the UI via an error toast.
window.addEventListener('error', this.onUnhandledError);
}
disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener('error', this.onUnhandledError);
}
private onUnhandledError(e: ErrorEvent): void {
errorMessage(e.error.message);
}
/**
* Shows a loading indicator while awaiting the given promise and returns its fulfilled value. If
* the promise is rejected, the rejection reason will be displayed in the UI as an error toast.
*
* This function can be called multiple times concurrently.
*/
static waitFor<T>(promise: Promise<T>): Promise<T> {
const scaffold = document.querySelector<CodesizeScaffoldSk>('codesize-scaffold-sk')!;
return scaffold.waitFor(promise);
}
private async waitFor<T>(promise: Promise<T>): Promise<T> {
this.busyTaskCount++;
this.spinner!.active = true;
try {
return await promise;
} catch (e) {
await errorMessage(e);
throw e;
} finally {
this.busyTaskCount--;
if (this.busyTaskCount === 0) {
this.spinner!.active = false;
// Puppeteer tests can wait for this event before taking screenshots to ensure that the
// page's contents are fully loaded.
this.dispatchEvent(new CustomEvent(END_BUSY_EVENT, { bubbles: true }));
}
}
}
}
define('codesize-scaffold-sk', CodesizeScaffoldSk);