Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  FaviconFeed.sys.mjs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { actionTypes as at } from "resource://activity-stream/common/Actions.mjs";
import { getDomain } from "resource:///modules/topsites/TippyTopProvider.sys.mjs";

// We use importESModule here instead of static import so that
// the Karma test environment won't choke on this module. This
// is because the Karma test environment already stubs out
// RemoteSettings, and overrides importESModule to be a no-op (which
// can't be done for a static import statement).

// eslint-disable-next-line mozilla/use-static-import
const { RemoteSettings } = ChromeUtils.importESModule(
  "resource://services-settings/remote-settings.sys.mjs"
);

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

const MIN_FAVICON_SIZE = 96;

export class FaviconFeed {
  constructor() {
    this._queryForRedirects = new Set();
  }

  /**
   * fetchIcon attempts to fetch a rich icon for the given url from two sources.
   * First, it looks up the tippy top feed, if it's still missing, then it queries
   * the places for rich icon with its most recent visit in order to deal with
   * the redirected visit. See Bug 1421428 for more details.
   */
  async fetchIcon(url) {
    // Avoid initializing and fetching icons if prefs are turned off
    if (!this.shouldFetchIcons) {
      return;
    }

    const site = await this.getSite(getDomain(url));
    if (!site) {
      if (!this._queryForRedirects.has(url)) {
        this._queryForRedirects.add(url);
        Services.tm.idleDispatchToMainThread(() =>
          this.fetchIconFromRedirects(url)
        );
      }
      return;
    }

    let iconUri = Services.io.newURI(site.image_url);
    // The #tippytop is to be able to identify them for telemetry.
    iconUri = iconUri.mutate().setRef("tippytop").finalize();
    await this.#setFaviconForPage(Services.io.newURI(url), iconUri);
  }

  /**
   * Get the site tippy top data from Remote Settings.
   */
  async getSite(domain) {
    const sites = await this.tippyTop.get({
      filters: { domain },
      syncIfEmpty: false,
    });
    return sites.length ? sites[0] : null;
  }

  /**
   * Get the tippy top collection from Remote Settings.
   */
  get tippyTop() {
    if (!this._tippyTop) {
      this._tippyTop = RemoteSettings("tippytop");
    }
    return this._tippyTop;
  }

  /**
   * Determine if we should be fetching and saving icons.
   */
  get shouldFetchIcons() {
    return Services.prefs.getBoolPref("browser.chrome.site_icons");
  }

  onAction(action) {
    switch (action.type) {
      case at.RICH_ICON_MISSING:
        this.fetchIcon(action.data.url);
        break;
    }
  }

  /**
   * Get favicon info (uri and size) for a uri from Places.
   *
   * @param uri {nsIURI} Page to check for favicon data
   * @returns A promise of an object (possibly null) containing the data
   */
  getFaviconInfo(uri) {
    return new Promise(resolve =>
      lazy.PlacesUtils.favicons.getFaviconDataForPage(
        uri,
        // Package up the icon data in an object if we have it; otherwise null
        (iconUri, faviconLength, favicon, mimeType, faviconSize) =>
          resolve(iconUri ? { iconUri, faviconSize } : null),
        lazy.NewTabUtils.activityStreamProvider.THUMB_FAVICON_SIZE
      )
    );
  }

  /**
   * Fetch favicon for a url by following its redirects in Places.
   *
   * This can improve the rich icon coverage for Top Sites since Places only
   * associates the favicon to the final url if the original one gets redirected.
   * Note this is not an urgent request, hence it is dispatched to the main
   * thread idle handler to avoid any possible performance impact.
   */
  async fetchIconFromRedirects(url) {
    const visitPaths = await this.#fetchVisitPaths(url);
    if (visitPaths.length > 1) {
      const lastVisit = visitPaths.pop();
      const redirectedUri = Services.io.newURI(lastVisit.url);
      const iconInfo = await this.getFaviconInfo(redirectedUri);
      if (iconInfo?.faviconSize >= MIN_FAVICON_SIZE) {
        try {
          lazy.PlacesUtils.favicons.copyFavicons(
            redirectedUri,
            Services.io.newURI(url),
            lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE
          );
        } catch (ex) {
          console.error(`Failed to copy favicon [${ex}]`);
        }
      }
    }
  }

  /**
   * Get favicon data for given URL from network.
   *
   * @param {nsIURI} faviconURI
   *        nsIURI for the favicon.
   * @return {nsIURI} data URL
   */
  async getFaviconDataURLFromNetwork(faviconURI) {
    let channel = lazy.NetUtil.newChannel({
      uri: faviconURI,
      loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      securityFlags:
        Ci.nsILoadInfo.SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT |
        Ci.nsILoadInfo.SEC_ALLOW_CHROME |
        Ci.nsILoadInfo.SEC_DISALLOW_SCRIPT,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
    });

    let resolver = Promise.withResolvers();

    lazy.NetUtil.asyncFetch(channel, async (input, status, request) => {
      if (!Components.isSuccessCode(status)) {
        resolver.resolve();
        return;
      }

      try {
        let data = lazy.NetUtil.readInputStream(input, input.available());
        let { contentType } = request.QueryInterface(Ci.nsIChannel);
        input.close();

        let buffer = new Uint8ClampedArray(data);
        let blob = new Blob([buffer], { type: contentType });
        let dataURL = await new Promise((resolve, reject) => {
          let reader = new FileReader();
          reader.addEventListener("load", () => resolve(reader.result));
          reader.addEventListener("error", reject);
          reader.readAsDataURL(blob);
        });
        resolver.resolve(Services.io.newURI(dataURL));
      } catch (e) {
        resolver.reject(e);
      }
    });

    return resolver.promise;
  }

  /**
   * Set favicon for page.
   *
   * @param {nsIURI} pageURI
   * @param {nsIURI} faviconURI
   */
  async #setFaviconForPage(pageURI, faviconURI) {
    try {
      // If the given faviconURI is data URL, set it as is.
      if (faviconURI.schemeIs("data")) {
        lazy.PlacesUtils.favicons
          .setFaviconForPage(pageURI, faviconURI, faviconURI)
          .catch(console.error);
        return;
      }

      // Try to find the favicon data from DB.
      const faviconInfo = await this.getFaviconInfo(pageURI);
      if (faviconInfo?.faviconSize) {
        // As valid favicon data is already stored for the page,
        // we don't have to update.
        return;
      }

      // Otherwise, fetch from network.
      lazy.PlacesUtils.favicons
        .setFaviconForPage(
          pageURI,
          faviconURI,
          await this.getFaviconDataURLFromNetwork(faviconURI)
        )
        .catch(console.error);
    } catch (ex) {
      console.error(`Failed to set favicon for page:${ex}`);
    }
  }

  /**
   * Fetches visit paths for a given URL from its most recent visit in Places.
   *
   * Note that this includes the URL itself as well as all the following
   * permenent&temporary redirected URLs if any.
   *
   * @param {String} a URL string
   *
   * @returns {Array} Returns an array containing objects as
   *   {int}    visit_id: ID of the visit in moz_historyvisits.
   *   {String} url: URL of the redirected URL.
   */
  async #fetchVisitPaths(url) {
    const query = `
    WITH RECURSIVE path(visit_id)
    AS (
      SELECT v.id
      FROM moz_places h
      JOIN moz_historyvisits v
        ON v.place_id = h.id
      WHERE h.url_hash = hash(:url) AND h.url = :url
        AND v.visit_date = h.last_visit_date

      UNION

      SELECT id
      FROM moz_historyvisits
      JOIN path
        ON visit_id = from_visit
      WHERE visit_type IN
        (${lazy.PlacesUtils.history.TRANSITIONS.REDIRECT_PERMANENT},
         ${lazy.PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY})
    )
    SELECT visit_id, (
      SELECT (
        SELECT url
        FROM moz_places
        WHERE id = place_id)
      FROM moz_historyvisits
      WHERE id = visit_id) AS url
    FROM path
  `;

    const visits =
      await lazy.NewTabUtils.activityStreamProvider.executePlacesQuery(query, {
        columns: ["visit_id", "url"],
        params: { url },
      });
    return visits;
  }
}

[ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge