FaviconFeed.jsm 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const {actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm");
  6. const {getDomain} = ChromeUtils.import("resource://activity-stream/lib/TippyTopProvider.jsm");
  7. const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
  8. ChromeUtils.defineModuleGetter(this, "PlacesUtils",
  9. "resource://gre/modules/PlacesUtils.jsm");
  10. ChromeUtils.defineModuleGetter(this, "Services",
  11. "resource://gre/modules/Services.jsm");
  12. ChromeUtils.defineModuleGetter(this, "NewTabUtils",
  13. "resource://gre/modules/NewTabUtils.jsm");
  14. const MIN_FAVICON_SIZE = 96;
  15. /**
  16. * Get favicon info (uri and size) for a uri from Places.
  17. *
  18. * @param uri {nsIURI} Page to check for favicon data
  19. * @returns A promise of an object (possibly null) containing the data
  20. */
  21. function getFaviconInfo(uri) {
  22. // Use 0 to get the biggest width available
  23. const preferredWidth = 0;
  24. return new Promise(resolve => PlacesUtils.favicons.getFaviconDataForPage(
  25. uri,
  26. // Package up the icon data in an object if we have it; otherwise null
  27. (iconUri, faviconLength, favicon, mimeType, faviconSize) =>
  28. resolve(iconUri ? {iconUri, faviconSize} : null),
  29. preferredWidth));
  30. }
  31. /**
  32. * Fetches visit paths for a given URL from its most recent visit in Places.
  33. *
  34. * Note that this includes the URL itself as well as all the following
  35. * permenent&temporary redirected URLs if any.
  36. *
  37. * @param {String} a URL string
  38. *
  39. * @returns {Array} Returns an array containing objects as
  40. * {int} visit_id: ID of the visit in moz_historyvisits.
  41. * {String} url: URL of the redirected URL.
  42. */
  43. async function fetchVisitPaths(url) {
  44. const query = `
  45. WITH RECURSIVE path(visit_id)
  46. AS (
  47. SELECT v.id
  48. FROM moz_places h
  49. JOIN moz_historyvisits v
  50. ON v.place_id = h.id
  51. WHERE h.url_hash = hash(:url) AND h.url = :url
  52. AND v.visit_date = h.last_visit_date
  53. UNION
  54. SELECT id
  55. FROM moz_historyvisits
  56. JOIN path
  57. ON visit_id = from_visit
  58. WHERE visit_type IN
  59. (${PlacesUtils.history.TRANSITIONS.REDIRECT_PERMANENT},
  60. ${PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY})
  61. )
  62. SELECT visit_id, (
  63. SELECT (
  64. SELECT url
  65. FROM moz_places
  66. WHERE id = place_id)
  67. FROM moz_historyvisits
  68. WHERE id = visit_id) AS url
  69. FROM path
  70. `;
  71. const visits = await NewTabUtils.activityStreamProvider.executePlacesQuery(query, {
  72. columns: ["visit_id", "url"],
  73. params: {url},
  74. });
  75. return visits;
  76. }
  77. /**
  78. * Fetch favicon for a url by following its redirects in Places.
  79. *
  80. * This can improve the rich icon coverage for Top Sites since Places only
  81. * associates the favicon to the final url if the original one gets redirected.
  82. * Note this is not an urgent request, hence it is dispatched to the main
  83. * thread idle handler to avoid any possible performance impact.
  84. */
  85. async function fetchIconFromRedirects(url) {
  86. const visitPaths = await fetchVisitPaths(url);
  87. if (visitPaths.length > 1) {
  88. const lastVisit = visitPaths.pop();
  89. const redirectedUri = Services.io.newURI(lastVisit.url);
  90. const iconInfo = await getFaviconInfo(redirectedUri);
  91. if (iconInfo && iconInfo.faviconSize >= MIN_FAVICON_SIZE) {
  92. PlacesUtils.favicons.setAndFetchFaviconForPage(
  93. Services.io.newURI(url),
  94. iconInfo.iconUri,
  95. false,
  96. PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
  97. null,
  98. Services.scriptSecurityManager.getSystemPrincipal()
  99. );
  100. }
  101. }
  102. }
  103. this.FaviconFeed = class FaviconFeed {
  104. constructor() {
  105. this._queryForRedirects = new Set();
  106. }
  107. /**
  108. * fetchIcon attempts to fetch a rich icon for the given url from two sources.
  109. * First, it looks up the tippy top feed, if it's still missing, then it queries
  110. * the places for rich icon with its most recent visit in order to deal with
  111. * the redirected visit. See Bug 1421428 for more details.
  112. */
  113. async fetchIcon(url) {
  114. // Avoid initializing and fetching icons if prefs are turned off
  115. if (!this.shouldFetchIcons) {
  116. return;
  117. }
  118. const site = await this.getSite(getDomain(url));
  119. if (!site) {
  120. if (!this._queryForRedirects.has(url)) {
  121. this._queryForRedirects.add(url);
  122. Services.tm.idleDispatchToMainThread(() => fetchIconFromRedirects(url));
  123. }
  124. return;
  125. }
  126. let iconUri = Services.io.newURI(site.image_url);
  127. // The #tippytop is to be able to identify them for telemetry.
  128. iconUri = iconUri.mutate().setRef("tippytop").finalize();
  129. PlacesUtils.favicons.setAndFetchFaviconForPage(
  130. Services.io.newURI(url),
  131. iconUri,
  132. false,
  133. PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
  134. null,
  135. Services.scriptSecurityManager.getSystemPrincipal()
  136. );
  137. }
  138. /**
  139. * Get the site tippy top data from Remote Settings.
  140. */
  141. async getSite(domain) {
  142. const sites = await this.tippyTop.get({
  143. filters: {domain},
  144. syncIfEmpty: false,
  145. });
  146. return sites.length ? sites[0] : null;
  147. }
  148. /**
  149. * Get the tippy top collection from Remote Settings.
  150. */
  151. get tippyTop() {
  152. if (!this._tippyTop) {
  153. this._tippyTop = RemoteSettings("tippytop");
  154. }
  155. return this._tippyTop;
  156. }
  157. /**
  158. * Determine if we should be fetching and saving icons.
  159. */
  160. get shouldFetchIcons() {
  161. return Services.prefs.getBoolPref("browser.chrome.site_icons");
  162. }
  163. onAction(action) {
  164. switch (action.type) {
  165. case at.RICH_ICON_MISSING:
  166. this.fetchIcon(action.data.url);
  167. break;
  168. }
  169. }
  170. };
  171. const EXPORTED_SYMBOLS = ["FaviconFeed", "fetchIconFromRedirects"];