chromium/chrome/browser/resources/settings/controls/settings_idle_load.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * settings-idle-load is a simple variant of dom-if designed for lazy
 * loading and rendering of elements that are accessed imperatively. A URL is
 * given that holds the elements to be loaded lazily.
 */
import {assert} from '//resources/js/assert.js';
import type {TemplateInstanceBase} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {html, PolymerElement, templatize} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {ensureLazyLoaded} from '../ensure_lazy_loaded.js';

export class SettingsIdleLoadElement extends PolymerElement {
  static get is() {
    return 'settings-idle-load';
  }

  static get template() {
    return html`<slot></slot>`;
  }

  private child_: Element|null = null;
  private instance_: TemplateInstanceBase|null = null;
  private idleCallback_: number = 0;
  private loading_: Promise<Element>|null = null;

  override connectedCallback() {
    super.connectedCallback();

    this.idleCallback_ = requestIdleCallback(() => {
      this.get();
    });
  }

  override disconnectedCallback() {
    super.disconnectedCallback();

    // No-op if callback already fired.
    cancelIdleCallback(this.idleCallback_);
  }

  /**
   * @return Resolves with the stamped child element after
   *     the lazy module has been loaded.
   */
  private requestLazyModule_(): Promise<Element> {
    return new Promise((resolve, reject) => {
      ensureLazyLoaded().then(() => {
        const template =
            (this.shadowRoot!.querySelector('slot')!
                 .assignedNodes({flatten: true})
                 .filter(n => n.nodeType === Node.ELEMENT_NODE)[0]) as
            HTMLTemplateElement;

        const TemplateClass = templatize(template, this, {
          mutableData: false,
          forwardHostProp: this._forwardHostPropV2,
        });

        this.instance_ = new TemplateClass();

        assert(!this.child_);
        this.child_ = this.instance_.root.firstElementChild;

        this.parentNode!.insertBefore(this.instance_.root, this);
        resolve(this.child_!);

        this.dispatchEvent(
            new CustomEvent('lazy-loaded', {bubbles: true, composed: true}));
      }, reject);
    });
  }

  /**
   * @return Child element which has been stamped into the DOM tree.
   */
  override get(): Promise<Element> {
    if (this.loading_) {
      return this.loading_;
    }

    this.loading_ = this.requestLazyModule_();
    return this.loading_;
  }

  /* eslint-disable-next-line @typescript-eslint/naming-convention */
  private _forwardHostPropV2(prop: string, value: any) {
    if (this.instance_) {
      this.instance_.forwardHostProp(prop, value);
    }
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-idle-load': SettingsIdleLoadElement;
  }
}

customElements.define(SettingsIdleLoadElement.is, SettingsIdleLoadElement);