// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/favicon/core/favicon_handler.h"

#include <algorithm>
#include <cmath>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon/core/features.h"
#include "components/favicon_base/favicon_util.h"
#include "components/favicon_base/select_favicon_frames.h"
#include "skia/ext/image_operations.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_util.h"

namespace favicon {
namespace {

const int kLargestIconSize = 192;

// Return true if |bitmap_result| is expired.
bool IsExpired(const favicon_base::FaviconRawBitmapResult& bitmap_result) {
  return bitmap_result.expired;
}

// Return true if |bitmap_result| is valid.
bool IsValid(const favicon_base::FaviconRawBitmapResult& bitmap_result) {
  return bitmap_result.is_valid();
}

// Returns true if |bitmap_results| is non-empty and:
// - At least one of the bitmaps in |bitmap_results| is expired
// OR
// - |bitmap_results| is missing favicons for |desired_size_in_dip| and one of
//   the scale factors in favicon_base::GetFaviconScales().
bool HasExpiredOrIncompleteResult(
    int desired_size_in_dip,
    const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) {
  if (bitmap_results.empty())
    return false;

  // Check if at least one of the bitmaps is expired.
  std::vector<favicon_base::FaviconRawBitmapResult>::const_iterator it =
      std::find_if(bitmap_results.begin(), bitmap_results.end(), IsExpired);
  if (it != bitmap_results.end())
    return true;

  // Any favicon size is good if the desired size is 0.
  if (desired_size_in_dip == 0)
    return false;

  // Check if the favicon for at least one of the scale factors is missing.
  // |bitmap_results| should always be complete for data inserted by
  // FaviconHandler as the FaviconHandler stores favicons resized to all
  // of favicon_base::GetFaviconScales() into the history backend.
  // Examples of when |bitmap_results| can be incomplete:
  // - Favicons inserted into the history backend by sync.
  // - Favicons for imported bookmarks.
  std::vector<gfx::Size> favicon_sizes;
  for (const auto& bitmap_result : bitmap_results)
    favicon_sizes.push_back(bitmap_result.pixel_size);

  std::vector<float> favicon_scales = favicon_base::GetFaviconScales();
  for (float favicon_scale : favicon_scales) {
    int edge_size_in_pixel = std::ceil(desired_size_in_dip * favicon_scale);
    auto it = std::find(favicon_sizes.begin(), favicon_sizes.end(),
                        gfx::Size(edge_size_in_pixel, edge_size_in_pixel));
    if (it == favicon_sizes.end())
      return true;
  }
  return false;
}

// Returns true if at least one of |bitmap_results| is valid.
bool HasValidResult(
    const std::vector<favicon_base::FaviconRawBitmapResult>& bitmap_results) {
  return std::find_if(bitmap_results.begin(), bitmap_results.end(), IsValid) !=
      bitmap_results.end();
}

std::vector<int> GetDesiredPixelSizes(
    FaviconDriverObserver::NotificationIconType handler_type) {
  switch (handler_type) {
    case FaviconDriverObserver::NON_TOUCH_16_DIP: {
      std::vector<int> pixel_sizes;
      for (float scale_factor : favicon_base::GetFaviconScales()) {
        pixel_sizes.push_back(
            static_cast<int>(ceil(scale_factor * gfx::kFaviconSize)));
      }
      return pixel_sizes;
    }
    case FaviconDriverObserver::NON_TOUCH_LARGEST:
    case FaviconDriverObserver::TOUCH_LARGEST:
      return std::vector<int>(1U, kLargestIconSize);
  }
  NOTREACHED();
  return std::vector<int>();
}

bool FaviconURLEquals(const FaviconURL& lhs, const FaviconURL& rhs) {
  return lhs.icon_url == rhs.icon_url && lhs.icon_type == rhs.icon_type &&
         lhs.icon_sizes == rhs.icon_sizes;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////

// static
FaviconHandler::FaviconCandidate
FaviconHandler::FaviconCandidate::FromFaviconURL(
    const favicon::FaviconURL& favicon_url,
    const std::vector<int>& desired_pixel_sizes) {
  FaviconCandidate candidate;
  candidate.icon_url = favicon_url.icon_url;
  candidate.icon_type = favicon_url.icon_type;
  // TODO(crbug.com/705900): For candidates without explicit size information,
  // sizes could be inferred for the most common cases. Namely, .ico files tend
  // to contain the 16x16 bitmap, which would allow to improve the
  // prioritization on desktop.
  SelectFaviconFrameIndices(favicon_url.icon_sizes, desired_pixel_sizes,
                            /*best_indices=*/nullptr, &candidate.score);
  return candidate;
}

////////////////////////////////////////////////////////////////////////////////

FaviconHandler::FaviconHandler(
    FaviconService* service,
    Delegate* delegate,
    FaviconDriverObserver::NotificationIconType handler_type)
    : handler_type_(handler_type),
      got_favicon_from_history_(false),
      initial_history_result_expired_or_incomplete_(false),
      redownload_icons_(false),
      icon_types_(FaviconHandler::GetIconTypesFromHandlerType(handler_type)),
      download_largest_icon_(
          handler_type == FaviconDriverObserver::NON_TOUCH_LARGEST ||
          handler_type == FaviconDriverObserver::TOUCH_LARGEST),
      candidates_received_(false),
      error_other_than_404_found_(false),
      notification_icon_type_(favicon_base::IconType::kInvalid),
      service_(service),
      delegate_(delegate),
      current_candidate_index_(0u) {
  DCHECK(delegate_);
}

FaviconHandler::~FaviconHandler() {
}

// static
favicon_base::IconTypeSet FaviconHandler::GetIconTypesFromHandlerType(
    FaviconDriverObserver::NotificationIconType handler_type) {
  switch (handler_type) {
    case FaviconDriverObserver::NON_TOUCH_16_DIP:
    case FaviconDriverObserver::NON_TOUCH_LARGEST:
      return {favicon_base::IconType::kFavicon};
    case FaviconDriverObserver::TOUCH_LARGEST:
      return {favicon_base::IconType::kTouchIcon,
              favicon_base::IconType::kTouchPrecomposedIcon,
              favicon_base::IconType::kWebManifestIcon};
  }
  return {};
}

void FaviconHandler::FetchFavicon(const GURL& page_url, bool is_same_document) {
  cancelable_task_tracker_for_page_url_.TryCancelAll();
  cancelable_task_tracker_for_candidates_.TryCancelAll();

  // We generally clear |page_urls_| and start clean unless there are obvious
  // reasons to think URLs share favicons: the navigation must be within the
  // same document (e.g. fragment navigation) AND it happened so early that no
  // candidates were received for the previous URL(s) (e.g. redirect-like
  // history.replaceState() during page load).
  if (!is_same_document || candidates_received_) {
    page_urls_.clear();
  }
  page_urls_.insert(page_url);
  last_page_url_ = page_url;

  initial_history_result_expired_or_incomplete_ = false;
  redownload_icons_ = false;
  got_favicon_from_history_ = false;
  manifest_download_request_.Cancel();
  image_download_request_.Cancel();
  candidates_received_ = false;
  manifest_url_ = GURL();
  non_manifest_original_candidates_.clear();
  final_candidates_.reset();
  notification_icon_url_ = GURL();
  notification_icon_type_ = favicon_base::IconType::kInvalid;
  current_candidate_index_ = 0u;
  best_favicon_ = DownloadedFavicon();

  // Request the favicon from the history service. In parallel to this the
  // renderer is going to notify us (well WebContents) when the favicon url is
  // available. We use |last_page_url_| specifically (regardless of other
  // possible values in |page_urls_|) because we want to use the most
  // up-to-date / latest URL for DB lookups, which is the page URL for which
  // we get <link rel="icon"> candidates (FaviconHandler::OnUpdateCandidates()).
  service_->GetFaviconForPageURL(
      last_page_url_, icon_types_, preferred_icon_size(),
      base::Bind(&FaviconHandler::OnFaviconDataForInitialURLFromFaviconService,
                 base::Unretained(this)),
      &cancelable_task_tracker_for_page_url_);
}

bool FaviconHandler::UpdateFaviconCandidate(
    const DownloadedFavicon& downloaded_favicon) {
  if (downloaded_favicon.candidate.score > best_favicon_.candidate.score)
    best_favicon_ = downloaded_favicon;

  if (download_largest_icon_) {
    // The size of the downloaded icon may not match the declared size. It's
    // important to stop downloading if:
    // - current candidate is only candidate.
    // - next candidate has sizes attribute and it is not better than the best
    //   one observed so far, which means any following candidate should also
    //   be worse or equal too.
    // - next candidate doesn't have sizes attributes, which means further
    //   candidates don't have sizes attribute either (because the score lowest
    //   and hence get sorted last during prioritization). We stop immediately
    //   to avoid downloading them all, although we don't have the certainty
    //   that no better favicon is among them.
    return current_candidate_index_ + 1 >= final_candidates_->size() ||
           (*final_candidates_)[current_candidate_index_ + 1].score <=
               best_favicon_.candidate.score;
  } else {
    return best_favicon_.candidate.score == 1;
  }
}

void FaviconHandler::SetFavicon(const GURL& icon_url,
                                const gfx::Image& image,
                                favicon_base::IconType icon_type) {
  // Associate the icon to all URLs in |page_urls_|, which contains page URLs
  // within the same site/document that have been considered to reliably share
  // the same icon candidates.
  if (!delegate_->IsOffTheRecord())
    service_->SetFavicons(page_urls_, icon_url, icon_type, image);

  NotifyFaviconUpdated(icon_url, icon_type, image);
}

void FaviconHandler::MaybeDeleteFaviconMappings() {
  DCHECK(candidates_received_);
  DCHECK(got_favicon_from_history_);

  // The order of these conditions is important because we want the feature
  // state to be checked at the very end.
  if (!error_other_than_404_found_ &&
      notification_icon_type_ != favicon_base::IconType::kInvalid) {
    if (!delegate_->IsOffTheRecord())
      service_->DeleteFaviconMappings(page_urls_, notification_icon_type_);

    delegate_->OnFaviconDeleted(last_page_url_, handler_type_);

    notification_icon_url_ = GURL();
    notification_icon_type_ = favicon_base::IconType::kInvalid;
  }
}

void FaviconHandler::NotifyFaviconUpdated(
    const std::vector<favicon_base::FaviconRawBitmapResult>&
        favicon_bitmap_results) {
  DCHECK(!favicon_bitmap_results.empty());

  gfx::Image resized_image = favicon_base::SelectFaviconFramesFromPNGs(
      favicon_bitmap_results,
      favicon_base::GetFaviconScales(),
      preferred_icon_size());
  // The history service sends back results for a single icon URL and icon
  // type, so it does not matter which result we get |icon_url| and |icon_type|
  // from.
  const GURL icon_url = favicon_bitmap_results[0].icon_url;
  favicon_base::IconType icon_type = favicon_bitmap_results[0].icon_type;
  NotifyFaviconUpdated(icon_url, icon_type, resized_image);
}

void FaviconHandler::NotifyFaviconUpdated(const GURL& icon_url,
                                          favicon_base::IconType icon_type,
                                          const gfx::Image& image) {
  if (image.IsEmpty())
    return;

  gfx::Image image_with_adjusted_colorspace = image;
  favicon_base::SetFaviconColorSpace(&image_with_adjusted_colorspace);

  delegate_->OnFaviconUpdated(last_page_url_, handler_type_, icon_url,
                              icon_url != notification_icon_url_,
                              image_with_adjusted_colorspace);

  notification_icon_url_ = icon_url;
  notification_icon_type_ = icon_type;
}

void FaviconHandler::OnUpdateCandidates(
    const GURL& page_url,
    const std::vector<FaviconURL>& candidates,
    const GURL& manifest_url) {
  if (last_page_url_ != page_url)
    return;

  // |candidates| or |manifest_url| could have been modified via Javascript. If
  // neither changed, ignore the call.
  if (candidates_received_ && manifest_url_ == manifest_url &&
      (non_manifest_original_candidates_.size() == candidates.size() &&
       std::equal(candidates.begin(), candidates.end(),
                  non_manifest_original_candidates_.begin(),
                  &FaviconURLEquals))) {
    return;
  }

  candidates_received_ = true;
  error_other_than_404_found_ = false;
  non_manifest_original_candidates_ = candidates;
  final_candidates_.reset();
  cancelable_task_tracker_for_candidates_.TryCancelAll();
  manifest_download_request_.Cancel();
  image_download_request_.Cancel();
  current_candidate_index_ = 0u;
  best_favicon_ = DownloadedFavicon();
  manifest_url_ = manifest_url;

  // Check if the manifest was previously blacklisted (e.g. returned a 404) and
  // ignore the manifest URL if that's the case.
  if (!manifest_url_.is_empty() &&
      service_->WasUnableToDownloadFavicon(manifest_url_)) {
    DVLOG(1) << "Skip failed Manifest: " << manifest_url;
    manifest_url_ = GURL();
  }

  // If no manifest available, proceed with the regular candidates only.
  if (manifest_url_.is_empty()) {
    OnGotFinalIconURLCandidates(candidates);
    return;
  }

  // See if there is a cached favicon for the manifest. This will update the DB
  // mappings only if the manifest URL is cached.
  GetFaviconAndUpdateMappingsUnlessIncognito(
      /*icon_url=*/manifest_url_, favicon_base::IconType::kWebManifestIcon,
      base::Bind(&FaviconHandler::OnFaviconDataForManifestFromFaviconService,
                 base::Unretained(this)));
}

void FaviconHandler::OnFaviconDataForManifestFromFaviconService(
    const std::vector<favicon_base::FaviconRawBitmapResult>&
        favicon_bitmap_results) {
  // The database lookup for the page URL is guaranteed to be completed because
  // the HistoryBackend uses a SequencedTaskRunner, and we also know that
  // FetchFavicon() was called before OnUpdateCandidates().
  DCHECK(got_favicon_from_history_);

  bool has_valid_result = HasValidResult(favicon_bitmap_results);
  bool has_expired_or_incomplete_result =
      !has_valid_result || HasExpiredOrIncompleteResult(preferred_icon_size(),
                                                        favicon_bitmap_results);

  if (has_valid_result &&
      (notification_icon_url_ != manifest_url_ ||
       notification_icon_type_ != favicon_base::IconType::kWebManifestIcon)) {
    // There is a valid favicon. Notify any observers. It is useful to notify
    // the observers even if the favicon is expired or incomplete (incorrect
    // size) because temporarily showing the user an expired favicon or
    // streched favicon is preferable to showing the user the default favicon.
    NotifyFaviconUpdated(favicon_bitmap_results);
  }

  if (has_expired_or_incomplete_result) {
    manifest_download_request_.Reset(base::BindOnce(
        &FaviconHandler::OnDidDownloadManifest, base::Unretained(this)));
    delegate_->DownloadManifest(manifest_url_,
                                manifest_download_request_.callback());
  }
}

void FaviconHandler::OnDidDownloadManifest(
    const std::vector<FaviconURL>& candidates) {
  // Mark manifest download as finished.
  manifest_download_request_.Cancel();

  if (!candidates.empty()) {
    OnGotFinalIconURLCandidates(candidates);
    return;
  }

  // If either the downloading of the manifest failed, OR the manifest contains
  // no icons, proceed with the list of icons listed in the HTML.
  DVLOG(1) << "Could not fetch Manifest icons from " << manifest_url_
           << ", falling back to inlined ones, which are "
           << non_manifest_original_candidates_.size();

  service_->UnableToDownloadFavicon(manifest_url_);
  manifest_url_ = GURL();

  OnGotFinalIconURLCandidates(non_manifest_original_candidates_);
}

void FaviconHandler::OnGotFinalIconURLCandidates(
    const std::vector<FaviconURL>& candidates) {
  DCHECK(!final_candidates_);

  const std::vector<int> desired_pixel_sizes =
      GetDesiredPixelSizes(handler_type_);

  std::vector<FaviconCandidate> sorted_candidates;
  for (const FaviconURL& candidate : candidates) {
    if (!candidate.icon_url.is_empty() &&
        (icon_types_.count(candidate.icon_type) != 0)) {
      sorted_candidates.push_back(
          FaviconCandidate::FromFaviconURL(candidate, desired_pixel_sizes));
    }
  }

  std::stable_sort(sorted_candidates.begin(), sorted_candidates.end(),
                   &FaviconCandidate::CompareScore);

  final_candidates_ = std::move(sorted_candidates);

  if (got_favicon_from_history_)
    OnGotInitialHistoryDataAndIconURLCandidates();
}

// static
int FaviconHandler::GetMaximalIconSize(
    FaviconDriverObserver::NotificationIconType handler_type,
    bool candidates_from_web_manifest) {
  int max_size = 0;
  for (int size : GetDesiredPixelSizes(handler_type)) {
    max_size = std::max(max_size, size);
  }
  return max_size;
}

void FaviconHandler::OnGotInitialHistoryDataAndIconURLCandidates() {
  DCHECK(final_candidates_);
  DCHECK(got_favicon_from_history_);
  DCHECK_EQ(0U, current_candidate_index_);

  if (final_candidates_->empty()) {
    // The page lists no candidates that match our target |icon_types_|, so
    // check if any existing mappings should be deleted.
    MaybeDeleteFaviconMappings();
    return;
  }

  if (!initial_history_result_expired_or_incomplete_ &&
      current_candidate()->icon_url == notification_icon_url_ &&
      current_candidate()->icon_type == notification_icon_type_) {
    // - The data from history is valid and not expired.
    // - The icon URL of the history data matches one of the page's icon URLs.
    // - The icon URL of the history data matches the icon URL of the last
    //   OnFaviconAvailable() notification.
    // We are done. No additional downloads or history requests are needed.
    // TODO: Store all of the icon URLs associated with a page in history so
    // that we can check whether the page's icon URLs match the page's icon URLs
    // at the time that the favicon data was stored to the history database.
    return;
  }

  DownloadCurrentCandidateOrAskFaviconService();
}

void FaviconHandler::OnDidDownloadFavicon(
    favicon_base::IconType icon_type,
    int id,
    int http_status_code,
    const GURL& image_url,
    const std::vector<SkBitmap>& bitmaps,
    const std::vector<gfx::Size>& original_bitmap_sizes) {
  // Mark download as finished.
  image_download_request_.Cancel();

  bool request_next_icon = true;
  if (bitmaps.empty()) {
    if (http_status_code == 404) {
      DVLOG(1) << "Failed to Download Favicon:" << image_url;
      service_->UnableToDownloadFavicon(image_url);
    } else if (http_status_code != 0) {
      error_other_than_404_found_ = true;
    }
  } else {
    float score = 0.0f;
    gfx::ImageSkia image_skia;
    if (download_largest_icon_) {
      std::vector<size_t> best_indices;
      SelectFaviconFrameIndices(original_bitmap_sizes,
                                GetDesiredPixelSizes(handler_type_),
                                &best_indices, &score);
      DCHECK_EQ(1U, best_indices.size());
      image_skia =
          gfx::ImageSkia::CreateFrom1xBitmap(bitmaps[best_indices.front()]);
    } else {
      image_skia = CreateFaviconImageSkia(bitmaps,
                                          original_bitmap_sizes,
                                          preferred_icon_size(),
                                          &score);
    }

    if (!image_skia.isNull()) {
      // The downloaded icon is still valid when there is no FaviconURL update
      // during the downloading.
      DownloadedFavicon downloaded_favicon;
      downloaded_favicon.image = gfx::Image(image_skia);
      downloaded_favicon.candidate.icon_url = image_url;
      downloaded_favicon.candidate.icon_type = icon_type;
      downloaded_favicon.candidate.score = score;
      request_next_icon = !UpdateFaviconCandidate(downloaded_favicon);
    }
  }

  if (request_next_icon &&
      current_candidate_index_ + 1 < final_candidates_->size()) {
    // Process the next candidate.
    ++current_candidate_index_;
    DownloadCurrentCandidateOrAskFaviconService();
  } else {
    if (best_favicon_.candidate.icon_type == favicon_base::IconType::kInvalid) {
      // No valid icon found, so check if mappings should be deleted.
      MaybeDeleteFaviconMappings();
    } else {
      // We have either found the ideal candidate or run out of candidates.
      // No more icons to request, set the favicon from the candidate. The
      // manifest URL, if available, is used instead of the icon URL.
      SetFavicon(manifest_url_.is_empty() ? best_favicon_.candidate.icon_url
                                          : manifest_url_,
                 best_favicon_.image,
                 manifest_url_.is_empty()
                     ? best_favicon_.candidate.icon_type
                     : favicon_base::IconType::kWebManifestIcon);
    }
    // Clear download related state.
    current_candidate_index_ = final_candidates_->size();
    best_favicon_ = DownloadedFavicon();
  }
}

const std::vector<GURL> FaviconHandler::GetIconURLs() const {
  std::vector<GURL> icon_urls;
  for (const FaviconCandidate& candidate : *final_candidates_)
    icon_urls.push_back(candidate.icon_url);
  return icon_urls;
}

bool FaviconHandler::HasPendingTasksForTest() {
  return !image_download_request_.IsCancelled() ||
         !manifest_download_request_.IsCancelled() ||
         cancelable_task_tracker_for_page_url_.HasTrackedTasks() ||
         cancelable_task_tracker_for_candidates_.HasTrackedTasks();
}

void FaviconHandler::OnFaviconDataForInitialURLFromFaviconService(
    const std::vector<favicon_base::FaviconRawBitmapResult>&
        favicon_bitmap_results) {
  got_favicon_from_history_ = true;
  bool has_valid_result = HasValidResult(favicon_bitmap_results);
  initial_history_result_expired_or_incomplete_ =
      !has_valid_result ||
      HasExpiredOrIncompleteResult(preferred_icon_size(),
                                   favicon_bitmap_results);
  redownload_icons_ = initial_history_result_expired_or_incomplete_ &&
                      !favicon_bitmap_results.empty();

  if (has_valid_result) {
    // Propagate mappings to all redirects, in case the redirect chain is
    // different from the one observed the previous time the page URL was
    // visited.
    //
    // Do the propagation now because we want the propagation to occur in all
    // scenarios and this is an easy way of guaranteeing it. For instance, we
    // want the propagation to occur when:
    // - The favicon in the database is expired.
    // AND
    // - Redownloading the favicon fails with a non-404 error code.
    if (!delegate_->IsOffTheRecord() &&
        base::FeatureList::IsEnabled(kAllowPropagationOfFaviconCacheHits)) {
      service_->CloneFaviconMappingsForPages(last_page_url_, icon_types_,
                                             page_urls_);
    }

    // The db knows the favicon (although it may be out of date). Set the
    // favicon now, and if the favicon turns out to be expired (or the wrong
    // url) we'll fetch later on. This way the user doesn't see a flash of the
    // default favicon.
    NotifyFaviconUpdated(favicon_bitmap_results);
  }

  if (final_candidates_)
    OnGotInitialHistoryDataAndIconURLCandidates();
}

void FaviconHandler::DownloadCurrentCandidateOrAskFaviconService() {
  DCHECK(image_download_request_.IsCancelled());
  DCHECK(manifest_download_request_.IsCancelled());
  DCHECK(current_candidate());

  const GURL icon_url = current_candidate()->icon_url;
  const favicon_base::IconType icon_type = current_candidate()->icon_type;
  // If the icons listed in a manifest are being processed, skip the cache
  // lookup for |icon_url| since the manifest's URL is used for caching, not the
  // icon URL, and this lookup has happened earlier.
  if (redownload_icons_ || !manifest_url_.is_empty()) {
    // We have the mapping, but the favicon is out of date. Download it now.
    ScheduleImageDownload(icon_url, icon_type);
  } else {
    GetFaviconAndUpdateMappingsUnlessIncognito(
        icon_url, icon_type,
        base::Bind(&FaviconHandler::OnFaviconData, base::Unretained(this)));
  }
}

void FaviconHandler::GetFaviconAndUpdateMappingsUnlessIncognito(
    const GURL& icon_url,
    favicon_base::IconType icon_type,
    const favicon_base::FaviconResultsCallback& callback) {
  // We don't know the favicon, but we may have previously downloaded the
  // favicon for another page that shares the same favicon. Ask for the
  // favicon given the favicon URL.
  if (delegate_->IsOffTheRecord()) {
    service_->GetFavicon(icon_url, icon_type, preferred_icon_size(), callback,
                         &cancelable_task_tracker_for_candidates_);
  } else {
    // Ask the history service for the icon. This does two things:
    // 1. Attempts to fetch the favicon data from the database.
    // 2. If the favicon exists in the database, this updates the database to
    //    include the mapping between the page url and the favicon url.
    // This is asynchronous. The history service will call back when done.
    service_->UpdateFaviconMappingsAndFetch(
        page_urls_, icon_url, icon_type, preferred_icon_size(), callback,
        &cancelable_task_tracker_for_candidates_);
  }
}

void FaviconHandler::OnFaviconData(const std::vector<
    favicon_base::FaviconRawBitmapResult>& favicon_bitmap_results) {
  bool has_valid_result = HasValidResult(favicon_bitmap_results);
  bool has_expired_or_incomplete_result =
      !has_valid_result || HasExpiredOrIncompleteResult(preferred_icon_size(),
                                                        favicon_bitmap_results);

  if (has_valid_result) {
    // There is a valid favicon. Notify any observers. It is useful to notify
    // the observers even if the favicon is expired or incomplete (incorrect
    // size) because temporarily showing the user an expired favicon or
    // streched favicon is preferable to showing the user the default favicon.
    NotifyFaviconUpdated(favicon_bitmap_results);
  }

  if (has_expired_or_incomplete_result) {
    ScheduleImageDownload(current_candidate()->icon_url,
                          current_candidate()->icon_type);
  }
}

void FaviconHandler::ScheduleImageDownload(const GURL& image_url,
                                           favicon_base::IconType icon_type) {
  DCHECK(image_url.is_valid());
  // Note that CancelableCallback starts cancelled.
  DCHECK(image_download_request_.IsCancelled())
      << "More than one ongoing download";
  if (service_->WasUnableToDownloadFavicon(image_url)) {
    DVLOG(1) << "Skip Failed FavIcon: " << image_url;
    OnDidDownloadFavicon(icon_type, 0, 0, image_url, std::vector<SkBitmap>(),
                         std::vector<gfx::Size>());
    return;
  }
  image_download_request_.Reset(
      base::BindOnce(&FaviconHandler::OnDidDownloadFavicon,
                     base::Unretained(this), icon_type));
  // A max bitmap size is specified to avoid receiving huge bitmaps in
  // OnDidDownloadFavicon(). See FaviconDriver::StartDownload()
  // for more details about the max bitmap size.
  const int download_id = delegate_->DownloadImage(
      image_url, GetMaximalIconSize(handler_type_, !manifest_url_.is_empty()),
      image_download_request_.callback());
  DCHECK_NE(download_id, 0);
}

}  // namespace favicon
