|  | // 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::ScaleFactor resource_scale_factor = | 
|  | ui::GetSupportedScaleFactor(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::GetSupportedScaleFactor(scale_factor)); | 
|  | } |