chromium/ui/webui/resources/cr_components/history_clusters/page_favicon.ts

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

import '//resources/cr_elements/cr_auto_img/cr_auto_img.js';

import {PageImageServiceBrowserProxy} from '//resources/cr_components/page_image_service/browser_proxy.js';
import {ClientId as PageImageServiceClientId} from '//resources/cr_components/page_image_service/page_image_service.mojom-webui.js';
import {getFaviconForPageURL} from '//resources/js/icon.js';
import {loadTimeData} from '//resources/js/load_time_data.js';
import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js';
import type {Url} from '//resources/mojo/url/mojom/url.mojom-webui.js';

import {getCss} from './page_favicon.css.js';
import {getHtml} from './page_favicon.html.js';

/**
 * @fileoverview This file provides a custom element displaying a page favicon.
 */

declare global {
  interface HTMLElementTagNameMap {
    'page-favicon': PageFaviconElement;
  }
}

/**
 * TODO(tommycli): This element should be renamed to reflect the reality that
 * it's used to both render the visit's "important image" if it exists, and
 * falls back to the favicon if it doesn't exist.
 */
export class PageFaviconElement extends CrLitElement {
  static get is() {
    return 'page-favicon';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      /**
       * Whether the cluster is in the side panel.
       */
      inSidePanel_: {
        type: Boolean,
        reflect: true,
      },

      /**
       * The URL for which the favicon is shown.
       */
      url: {type: Object},

      /**
       * Whether this visit is known to sync already. Used for the purpose of
       * fetching higher quality favicons in that case.
       */
      isKnownToSync: {type: Boolean},

      /**
       * The URL of the representative image for the page. Not every page has
       * this defined, in which case we fallback to the favicon.
       */
      imageUrl_: {type: Object},

      isImageCover_: {
        type: Boolean,
        reflect: true,
      },
    };
  }

  //============================================================================
  // Properties
  //============================================================================

  isKnownToSync: boolean = false;
  url: Url|null = null;
  protected imageUrl_: Url|null = null;
  protected inSidePanel_: boolean = loadTimeData.getBoolean('inSidePanel');
  protected isImageCover_: boolean =
      loadTimeData.getBoolean('isHistoryClustersImageCover');

  //============================================================================
  // Helper methods
  //============================================================================

  getImageUrlForTesting(): Url|null {
    return this.imageUrl_;
  }


  override willUpdate(changedProperties: PropertyValues<this>) {
    super.willUpdate(changedProperties);

    if (changedProperties.has('url') ||
        (changedProperties as Map<PropertyKey, unknown>).has('imageUrl_')) {
      if ((this.imageUrl_ && this.imageUrl_.url) || !this.url) {
        // Pages with a pre-set image URL or no favicon URL don't show the
        // favicon.
        this.style.setProperty('background-image', '');
      } else {
        this.style.setProperty(
            'background-image',
            getFaviconForPageURL(
                this.url.url, this.isKnownToSync, '',
                /* --favicon-size */ 16));
      }
    }

    if (changedProperties.has('url') ||
        changedProperties.has('isKnownToSync')) {
      this.urlAndIsKnownToSyncChanged_();
    }
  }

  private async urlAndIsKnownToSyncChanged_() {
    if (!this.url || !this.isKnownToSync ||
        !loadTimeData.getBoolean('isHistoryClustersImagesEnabled')) {
      this.imageUrl_ = null;
      return;
    }

    // Fetch the representative image for this page, if possible.
    const {result} =
        await PageImageServiceBrowserProxy.getInstance()
            .handler.getPageImageUrl(
                PageImageServiceClientId.Journeys, this.url,
                {suggestImages: true, optimizationGuideImages: true});
    if (result) {
      this.imageUrl_ = result.imageUrl;
    } else {
      // We must reset imageUrl_ to null, because sometimes the Virtual DOM will
      // reuse the same element for the infinite scrolling list.
      this.imageUrl_ = null;
    }
  }
}

customElements.define(PageFaviconElement.is, PageFaviconElement);