chromium/ash/webui/common/resources/cr_elements/cr_auto_img/cr_auto_img.ts

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

/**
 * @fileoverview <cr-auto-img> is a specialized <img> that facilitates embedding
 * images into WebUIs via its auto-src attribute. <cr-auto-img> automatically
 * determines if the image is local (e.g. data: or chrome://) or external (e.g.
 * https://), and embeds the image directly or via the chrome://image data
 * source accordingly. Usage:
 *
 *   1. In C++ register |SanitizedImageSource| for your WebUI.
 *
 *   2. In HTML instantiate
 *
 *      <img is="cr-auto-img" auto-src="https://foo.com/bar.png">
 *
 *      If your image URL points to Google Photos storage, meaning it needs an
 *      auth token, you can use the is-google-photos attribute as follows:
 *
 *      <img is="cr-auto-img" auto-src="https://foo.com/bar.png"
 *          is-google-photos>
 *
 *      If you want the image to reset to an empty state when auto-src changes
 *      and the new image is still loading, set the clear-src attribute:
 *
 *      <img is="cr-auto-img" auto-src="[[calculateSrc()]]" clear-src>
 *
 *      If you want your image to be always encoded as a static image (even if
 *      the source image is animated), set the static-encode attribute:
 *
 *      <img is="cr-auto-img" auto-src="https://foo.com/bar.png"
 *          static-encode>
 *
 *      Static images are encoded as PNG by default. If you want your image to
 *      be encoded as a Webp image, set the encode-type attribute to "webp".
 *
 *      <img is="cr-auto-img" auto-src="https://foo.com/bar.png"
 *          static-encode encode-type="webp">
 *
 * NOTE: Since <cr-auto-img> may use the chrome://image data source some images
 * may be transcoded to PNG.
 *
 * Forked from ui/webui/resources/cr_elements/cr_auto_img/cr_auto_img.ts
 */

const AUTO_SRC: string = 'auto-src';

const CLEAR_SRC: string = 'clear-src';

const IS_GOOGLE_PHOTOS: string = 'is-google-photos';

const STATIC_ENCODE: string = 'static-encode';

const ENCODE_TYPE: string = 'encode-type';

export class CrAutoImgElement extends HTMLImageElement {
  static get observedAttributes() {
    return [AUTO_SRC, IS_GOOGLE_PHOTOS, STATIC_ENCODE, ENCODE_TYPE];
  }

  attributeChangedCallback(
      name: string, oldValue: string|null, newValue: string|null) {
    if (name !== AUTO_SRC && name !== IS_GOOGLE_PHOTOS &&
        name !== STATIC_ENCODE && name !== ENCODE_TYPE) {
      return;
    }

    // Changes to |IS_GOOGLE_PHOTOS| are only interesting when the attribute is
    // being added or removed.
    if (name === IS_GOOGLE_PHOTOS &&
        ((oldValue === null) === (newValue === null))) {
      return;
    }

    if (this.hasAttribute(CLEAR_SRC)) {
      // Remove the src attribute so that the old image is not shown while the
      // new one is loading.
      this.removeAttribute('src');
    }

    let url = null;
    try {
      url = new URL(this.getAttribute(AUTO_SRC) || '');
    } catch (_) {
    }

    if (!url || url.protocol === 'chrome-untrusted:') {
      // Loading chrome-untrusted:// directly kills the renderer process.
      // Loading chrome-untrusted:// via the chrome://image data source
      // results in a broken image.
      this.removeAttribute('src');
      return;
    }
    if (url.protocol === 'data:' || url.protocol === 'chrome:') {
      this.src = url.href;
      return;
    }
    if (!this.hasAttribute(IS_GOOGLE_PHOTOS) &&
        !this.hasAttribute(STATIC_ENCODE) && !this.hasAttribute(ENCODE_TYPE)) {
      this.src = 'chrome://image?' + url.href;
      return;
    }

    this.src = `chrome://image?url=${encodeURIComponent(url.href)}`;
    if (this.hasAttribute(IS_GOOGLE_PHOTOS)) {
      this.src += `&isGooglePhotos=true`;
    }
    if (this.hasAttribute(STATIC_ENCODE)) {
      this.src += `&staticEncode=true`;
    }
    if (this.hasAttribute(ENCODE_TYPE)) {
      this.src += `&encodeType=${this.getAttribute(ENCODE_TYPE)}`;
    }
  }

  set autoSrc(src: string) {
    this.setAttribute(AUTO_SRC, src);
  }

  get autoSrc(): string {
    return this.getAttribute(AUTO_SRC) || '';
  }

  set clearSrc(_: string) {
    this.setAttribute(CLEAR_SRC, '');
  }

  get clearSrc(): string {
    return this.getAttribute(CLEAR_SRC) || '';
  }

  set isGooglePhotos(enabled: boolean) {
    if (enabled) {
      this.setAttribute(IS_GOOGLE_PHOTOS, '');
    } else {
      this.removeAttribute(IS_GOOGLE_PHOTOS);
    }
  }

  get isGooglePhotos(): boolean {
    return this.hasAttribute(IS_GOOGLE_PHOTOS);
  }

  set staticEncode(enabled: boolean) {
    if (enabled) {
      this.setAttribute(STATIC_ENCODE, '');
    } else {
      this.removeAttribute(STATIC_ENCODE);
    }
  }

  get staticEncode(): boolean {
    return this.hasAttribute(STATIC_ENCODE);
  }

  set encodeType(type: string) {
    if (type) {
      this.setAttribute(ENCODE_TYPE, type);
    } else {
      this.removeAttribute(ENCODE_TYPE);
    }
  }

  get encodeType(): string {
    return this.getAttribute(ENCODE_TYPE) || '';
  }
}

customElements.define('cr-auto-img', CrAutoImgElement, {extends: 'img'});