text: // 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 "chrome/browser/ui/webui/favicon_source.h"
  
  #include <cmath>
  
  #include "base/bind.h"
  #include "base/callback_helpers.h"
  #include "base/metrics/histogram_functions.h"
  #include "base/strings/string_number_conversions.h"
  #include "chrome/browser/favicon/favicon_service_factory.h"
  #include "chrome/browser/favicon/favicon_utils.h"
  #include "chrome/browser/favicon/history_ui_favicon_request_handler_factory.h"
  #include "chrome/browser/history/top_sites_factory.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/search/instant_service.h"
  #include "chrome/browser/ui/webui/webui_util.h"
  #include "chrome/common/url_constants.h"
  #include "chrome/common/webui_url_constants.h"
  #include "components/favicon/core/history_ui_favicon_request_handler.h"
  #include "components/favicon_base/favicon_url_parser.h"
  #include "components/history/core/browser/top_sites.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/web_contents.h"
  #include "extensions/browser/extension_registry.h"
  #include "extensions/common/constants.h"
  #include "extensions/common/manifest.h"
  #include "third_party/skia/include/core/SkBitmap.h"
  #include "ui/base/layout.h"
  #include "ui/base/resource/resource_bundle.h"
  #include "ui/base/webui/web_ui_util.h"
  #include "ui/gfx/codec/png_codec.h"
  #include "ui/native_theme/native_theme.h"
  #include "ui/resources/grit/ui_resources.h"
  #include "url/gurl.h"
  
  namespace {
  
  // web_contents->GetLastCommittedURL in general will not necessarily yield the
  // original URL that started the request, but we're only interested in verifying
  // if it was issued by a history page, for whom this is the case. If it is not
  // possible to obtain the URL, we return the empty GURL.
  GURL GetUnsafeRequestOrigin(const content::WebContents::Getter& wc_getter) {
    content::WebContents* web_contents = wc_getter.Run();
    return web_contents ? web_contents->GetLastCommittedURL() : GURL();
  }
  
  bool ParseHistoryUiOrigin(const GURL& url,
                            favicon::HistoryUiFaviconRequestOrigin* origin) {
    GURL history_url(chrome::kChromeUIHistoryURL);
    if (url == history_url) {
      *origin = favicon::HistoryUiFaviconRequestOrigin::kHistory;
      return true;
    }
    if (url == history_url.Resolve(chrome::kChromeUIHistorySyncedTabs)) {
      *origin = favicon::HistoryUiFaviconRequestOrigin::kHistorySyncedTabs;
      return true;
    }
    return false;
  }
  
  }  // namespace
  
  FaviconSource::FaviconSource(Profile* profile,
                               chrome::FaviconUrlFormat url_format)
      : profile_(profile->GetOriginalProfile()), url_format_(url_format) {}
  
  FaviconSource::~FaviconSource() {
  }
  
  std::string FaviconSource::GetSource() {
    switch (url_format_) {
      case chrome::FaviconUrlFormat::kFaviconLegacy:
        return chrome::kChromeUIFaviconHost;
      case chrome::FaviconUrlFormat::kFavicon2:
        return chrome::kChromeUIFavicon2Host;
    }
    NOTREACHED();
    return "";
  }
  
  void FaviconSource::StartDataRequest(
      const GURL& url,
      const content::WebContents::Getter& wc_getter,
      content::URLDataSource::GotDataCallback callback) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    const std::string path = content::URLDataSource::URLToRequestPath(url);
    favicon::FaviconService* favicon_service =
        FaviconServiceFactory::GetForProfile(profile_,
                                             ServiceAccessType::EXPLICIT_ACCESS);
    if (!favicon_service) {
      SendDefaultResponse(std::move(callback), wc_getter);
      return;
    }
  
    chrome::ParsedFaviconPath parsed;
    bool success = chrome::ParseFaviconPath(path, url_format_, &parsed);
    if (!success) {
      SendDefaultResponse(std::move(callback), wc_getter);
      return;
    }
  
    GURL page_url(parsed.page_url);
    GURL icon_url(parsed.icon_url);
    if (!page_url.is_valid() && !icon_url.is_valid()) {
      SendDefaultResponse(std::move(callback), wc_getter);
      return;
    }
  
    if (url_format_ == chrome::FaviconUrlFormat::kFaviconLegacy) {
      const extensions::Extension* extension =
          extensions::ExtensionRegistry::Get(profile_)
              ->enabled_extensions()
              .GetExtensionOrAppByURL(GetUnsafeRequestOrigin(wc_getter));
      if (extension) {
        base::UmaHistogramEnumeration("Extensions.FaviconResourceRequested",
                                      extension->GetType(),
                                      extensions::Manifest::NUM_LOAD_TYPES);
      }
    }
  
    int desired_size_in_pixel =
        std::ceil(parsed.size_in_dip * parsed.device_scale_factor);
  
    if (parsed.page_url.empty()) {
      // Request by icon url.
  
      // TODO(michaelbai): Change GetRawFavicon to support combination of
      // IconType.
      favicon_service->GetRawFavicon(
          icon_url, favicon_base::IconType::kFavicon, desired_size_in_pixel,
          base::BindOnce(&FaviconSource::OnFaviconDataAvailable,
                         base::Unretained(this), std::move(callback), parsed,
                         wc_getter),
          &cancelable_task_tracker_);
    } else {
      // Intercept requests for prepopulated pages if TopSites exists.
      scoped_refptr<history::TopSites> top_sites =
          TopSitesFactory::GetForProfile(profile_);
      if (top_sites) {
        for (const auto& prepopulated_page : top_sites->GetPrepopulatedPages()) {
          if (page_url == prepopulated_page.most_visited.url) {
            ui::ResourceScaleFactor resource_scale_factor =
                ui::GetSupportedResourceScaleFactor(parsed.device_scale_factor);
            std::move(callback).Run(
                ui::ResourceBundle::GetSharedInstance()
                    .LoadDataResourceBytesForScale(prepopulated_page.favicon_id,
                                                   resource_scale_factor));
            return;
          }
        }
      }
  
      favicon::HistoryUiFaviconRequestOrigin parsed_history_ui_origin;
      if (!parsed.allow_favicon_server_fallback ||
          !ParseHistoryUiOrigin(GetUnsafeRequestOrigin(wc_getter),
                                &parsed_history_ui_origin)) {
        // Request from local storage only.
        // TODO(victorvianna): Expose fallback_to_host in FaviconRequestHandler
        // API and move the explanatory comment for |fallback_to_host| here.
        const bool fallback_to_host = true;
        favicon_service->GetRawFaviconForPageURL(
            page_url, {favicon_base::IconType::kFavicon}, desired_size_in_pixel,
            fallback_to_host,
            base::BindOnce(&FaviconSource::OnFaviconDataAvailable,
                           base::Unretained(this), std::move(callback), parsed,
                           wc_getter),
            &cancelable_task_tracker_);
        return;
      }
  
      // Request from both local storage and favicon server using
      // HistoryUiFaviconRequestHandler.
      favicon::HistoryUiFaviconRequestHandler*
          history_ui_favicon_request_handler =
              HistoryUiFaviconRequestHandlerFactory::GetForBrowserContext(
                  profile_);
      if (!history_ui_favicon_request_handler) {
        SendDefaultResponse(std::move(callback), parsed, wc_getter);
        return;
      }
      history_ui_favicon_request_handler->GetRawFaviconForPageURL(
          page_url, desired_size_in_pixel,
          base::BindOnce(&FaviconSource::OnFaviconDataAvailable,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                         parsed, wc_getter),
          parsed_history_ui_origin);
    }
  }
  
  std::string FaviconSource::GetMimeType(const std::string&) {
    // We need to explicitly return a mime type, otherwise if the user tries to
    // drag the image they get no extension.
    return "image/png";
  }
  
  bool FaviconSource::AllowCaching() {
    return false;
  }
  
  bool FaviconSource::ShouldReplaceExistingSource() {
    // Leave the existing DataSource in place, otherwise we'll drop any pending
    // requests on the floor.
    return false;
  }
  
  bool FaviconSource::ShouldServiceRequest(
      const GURL& url,
      content::BrowserContext* browser_context,
      int render_process_id) {
    if (url.SchemeIs(chrome::kChromeSearchScheme)) {
      return InstantService::ShouldServiceRequest(url, browser_context,
                                                  render_process_id);
    }
    return URLDataSource::ShouldServiceRequest(url, browser_context,
                                               render_process_id);
  }
  
  ui::NativeTheme* FaviconSource::GetNativeTheme(
      const content::WebContents::Getter& wc_getter) {
    return webui::GetNativeTheme(wc_getter.Run());
  }
  
  void FaviconSource::OnFaviconDataAvailable(
      content::URLDataSource::GotDataCallback callback,
      const chrome::ParsedFaviconPath& parsed,
      const content::WebContents::Getter& wc_getter,
      const favicon_base::FaviconRawBitmapResult& bitmap_result) {
    if (bitmap_result.is_valid()) {
      // Forward the data along to the networking system.
      std::move(callback).Run(bitmap_result.bitmap_data.get());
    } else {
      SendDefaultResponse(std::move(callback), parsed, wc_getter);
    }
  }
  
  void FaviconSource::SendDefaultResponse(
      content::URLDataSource::GotDataCallback callback,
      const chrome::ParsedFaviconPath& parsed,
      const content::WebContents::Getter& wc_getter) {
    if (!parsed.show_fallback_monogram) {
      SendDefaultResponse(std::move(callback), parsed.size_in_dip,
                          parsed.device_scale_factor,
                          GetNativeTheme(wc_getter)->ShouldUseDarkColors());
      return;
    }
    int icon_size = std::ceil(parsed.size_in_dip * parsed.device_scale_factor);
    SkBitmap bitmap = favicon::GenerateMonogramFavicon(GURL(parsed.page_url),
                                                       icon_size, icon_size);
    std::vector<unsigned char> bitmap_data;
    bool result = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bitmap_data);
    DCHECK(result);
    std::move(callback).Run(base::RefCountedBytes::TakeVector(&bitmap_data));
  }
  
  void FaviconSource::SendDefaultResponse(
      content::URLDataSource::GotDataCallback callback,
      const content::WebContents::Getter& wc_getter) {
    SendDefaultResponse(std::move(callback), 16, 1.0f,
                        GetNativeTheme(wc_getter)->ShouldUseDarkColors());
  }
  
  void FaviconSource::SendDefaultResponse(
      content::URLDataSource::GotDataCallback callback,
      int size_in_dip,
      float scale_factor,
      bool dark_mode) {
    int resource_id;
    switch (size_in_dip) {
      case 64:
        resource_id =
            dark_mode ? IDR_DEFAULT_FAVICON_DARK_64 : IDR_DEFAULT_FAVICON_64;
        break;
      case 32:
        resource_id =
            dark_mode ? IDR_DEFAULT_FAVICON_DARK_32 : IDR_DEFAULT_FAVICON_32;
        break;
      default:
        resource_id = dark_mode ? IDR_DEFAULT_FAVICON_DARK : IDR_DEFAULT_FAVICON;
        break;
    }
    std::move(callback).Run(LoadIconBytes(scale_factor, resource_id));
  }
  
  base::RefCountedMemory* FaviconSource::LoadIconBytes(float scale_factor,
                                                       int resource_id) {
    return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytesForScale(
        resource_id, ui::GetSupportedResourceScaleFactor(scale_factor));
  }
  