diff options
Diffstat (limited to 'chromium/components/favicon/core/large_icon_service.cc')
-rw-r--r-- | chromium/components/favicon/core/large_icon_service.cc | 350 |
1 files changed, 259 insertions, 91 deletions
diff --git a/chromium/components/favicon/core/large_icon_service.cc b/chromium/components/favicon/core/large_icon_service.cc index fa42fe4d8f5..bf94e5b38ea 100644 --- a/chromium/components/favicon/core/large_icon_service.cc +++ b/chromium/components/favicon/core/large_icon_service.cc @@ -7,28 +7,149 @@ #include <memory> #include "base/bind.h" +#include "base/feature_list.h" #include "base/location.h" #include "base/logging.h" #include "base/macros.h" +#include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" +#include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/stringprintf.h" #include "base/task_runner.h" #include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/data_use_measurement/core/data_use_user_data.h" #include "components/favicon/core/favicon_service.h" #include "components/favicon_base/fallback_icon_style.h" #include "components/favicon_base/favicon_types.h" +#include "components/favicon_base/favicon_util.h" +#include "components/image_fetcher/core/request_metadata.h" #include "skia/ext/image_operations.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/geometry/size.h" +#include "url/url_canon.h" +namespace favicon { namespace { +// This feature is only used for accessing field trial parameters, not for +// switching on/off the code. +const base::Feature kLargeIconServiceFetchingFeature{ + "LargeIconServiceFetching", base::FEATURE_ENABLED_BY_DEFAULT}; + +const char kGoogleServerV2RequestFormat[] = + "https://t0.gstatic.com/faviconV2?" + "client=chrome&drop_404_icon=true&size=32&min_size=%d&max_size=64&" + "fallback_opts=TYPE,SIZE,URL&url=%s"; +const char kGoogleServerV2RequestFormatParam[] = "request_format"; + +GURL TrimPageUrlForGoogleServer(const GURL& page_url) { + if (!page_url.SchemeIsHTTPOrHTTPS() || page_url.HostIsIPAddress()) + return GURL(); + + url::Replacements<char> replacements; + replacements.ClearUsername(); + replacements.ClearPassword(); + replacements.ClearQuery(); + replacements.ClearRef(); + return page_url.ReplaceComponents(replacements); +} + +GURL GetRequestUrlForGoogleServerV2(const GURL& page_url, + int min_source_size_in_pixel) { + std::string url_format = base::GetFieldTrialParamValueByFeature( + kLargeIconServiceFetchingFeature, kGoogleServerV2RequestFormatParam); + + return GURL(base::StringPrintf( + url_format.empty() ? kGoogleServerV2RequestFormat : url_format.c_str(), + min_source_size_in_pixel, page_url.spec().c_str())); +} + +bool IsDbResultAdequate(const favicon_base::FaviconRawBitmapResult& db_result, + int min_source_size) { + return db_result.is_valid() && + db_result.pixel_size.width() == db_result.pixel_size.height() && + db_result.pixel_size.width() >= min_source_size; +} + +// Wraps the PNG data in |db_result| in a gfx::Image. If |desired_size| is not +// 0, the image gets decoded and resized to |desired_size| (in px). Must run on +// a background thread in production. +gfx::Image ResizeLargeIconOnBackgroundThread( + const favicon_base::FaviconRawBitmapResult& db_result, + int desired_size) { + gfx::Image image = gfx::Image::CreateFrom1xPNGBytes( + db_result.bitmap_data->front(), db_result.bitmap_data->size()); + + if (desired_size == 0 || db_result.pixel_size.width() == desired_size) { + return image; + } + + SkBitmap resized = skia::ImageOperations::Resize( + image.AsBitmap(), skia::ImageOperations::RESIZE_LANCZOS3, desired_size, + desired_size); + return gfx::Image::CreateFrom1xBitmap(resized); +} + +// Processes the |db_result| and writes the result into |raw_result| if +// |raw_result| is not nullptr or to |bitmap|, otherwise. If |db_result| is not +// valid or is smaller than |min_source_size|, the resulting fallback style is +// written into |fallback_icon_style|. +void ProcessIconOnBackgroundThread( + const favicon_base::FaviconRawBitmapResult& db_result, + int min_source_size, + int desired_size, + favicon_base::FaviconRawBitmapResult* raw_result, + SkBitmap* bitmap, + favicon_base::FallbackIconStyle* fallback_icon_style) { + if (IsDbResultAdequate(db_result, min_source_size)) { + gfx::Image image; + image = ResizeLargeIconOnBackgroundThread(db_result, desired_size); + + if (!image.IsEmpty()) { + if (raw_result) { + *raw_result = db_result; + if (desired_size != 0) + raw_result->pixel_size = gfx::Size(desired_size, desired_size); + raw_result->bitmap_data = image.As1xPNGBytes(); + } + if (bitmap) { + *bitmap = image.AsBitmap(); + } + return; + } + } + + if (!fallback_icon_style) + return; + + *fallback_icon_style = favicon_base::FallbackIconStyle(); + int fallback_icon_size = 0; + if (db_result.is_valid()) { + favicon_base::SetDominantColorAsBackground(db_result.bitmap_data, + fallback_icon_style); + // The size must be positive, we cap to 128 to avoid the sparse histogram + // to explode (having too many different values, server-side). Size 128 + // already indicates that there is a problem in the code, 128 px _should_ be + // enough in all current UI surfaces. + fallback_icon_size = db_result.pixel_size.width(); + DCHECK_GT(fallback_icon_size, 0); + fallback_icon_size = std::min(fallback_icon_size, 128); + } + UMA_HISTOGRAM_SPARSE_SLOWLY("Favicons.LargeIconService.FallbackSize", + fallback_icon_size); +} + // Processes the bitmap data returned from the FaviconService as part of a // LargeIconService request. class LargeIconWorker : public base::RefCountedThreadSafe<LargeIconWorker> { public: + // Exactly one of the callbacks is expected to be non-null. LargeIconWorker(int min_source_size_in_pixel, int desired_size_in_pixel, - favicon_base::LargeIconCallback callback, + favicon_base::LargeIconCallback raw_bitmap_callback, + favicon_base::LargeIconImageCallback image_callback, scoped_refptr<base::TaskRunner> background_task_runner, base::CancelableTaskTracker* tracker); @@ -37,39 +158,27 @@ class LargeIconWorker : public base::RefCountedThreadSafe<LargeIconWorker> { // ProcessIconOnBackgroundThread() so we do not perform complex image // operations on the UI thread. void OnIconLookupComplete( - const favicon_base::FaviconRawBitmapResult& bitmap_result); + const favicon_base::FaviconRawBitmapResult& db_result); private: friend class base::RefCountedThreadSafe<LargeIconWorker>; ~LargeIconWorker(); - // Must run on a background thread in production. - // Tries to resize |bitmap_result_| and pass the output to |callback_|. If - // that does not work, computes the icon fallback style and uses it to - // invoke |callback_|. This must be run on a background thread because image - // resizing and dominant color extraction can be expensive. - void ProcessIconOnBackgroundThread(); - - // Must run on a background thread in production. - // If |bitmap_result_| is square and large enough (>= |min_source_in_pixel_|), - // resizes it to |desired_size_in_pixel_| (but if |desired_size_in_pixel_| is - // 0 then don't resize). If successful, stores the resulting bitmap data - // into |resized_bitmap_result| and returns true. - bool ResizeLargeIconOnBackgroundThreadIfValid( - favicon_base::FaviconRawBitmapResult* resized_bitmap_result); - // Must run on the owner (UI) thread in production. // Invoked when ProcessIconOnBackgroundThread() is done. void OnIconProcessingComplete(); int min_source_size_in_pixel_; int desired_size_in_pixel_; - favicon_base::LargeIconCallback callback_; + favicon_base::LargeIconCallback raw_bitmap_callback_; + favicon_base::LargeIconImageCallback image_callback_; scoped_refptr<base::TaskRunner> background_task_runner_; base::CancelableTaskTracker* tracker_; - favicon_base::FaviconRawBitmapResult bitmap_result_; - std::unique_ptr<favicon_base::LargeIconResult> result_; + + favicon_base::FaviconRawBitmapResult raw_bitmap_result_; + SkBitmap bitmap_result_; + std::unique_ptr<favicon_base::FallbackIconStyle> fallback_icon_style_; DISALLOW_COPY_AND_ASSIGN(LargeIconWorker); }; @@ -77,98 +186,104 @@ class LargeIconWorker : public base::RefCountedThreadSafe<LargeIconWorker> { LargeIconWorker::LargeIconWorker( int min_source_size_in_pixel, int desired_size_in_pixel, - favicon_base::LargeIconCallback callback, + favicon_base::LargeIconCallback raw_bitmap_callback, + favicon_base::LargeIconImageCallback image_callback, scoped_refptr<base::TaskRunner> background_task_runner, base::CancelableTaskTracker* tracker) : min_source_size_in_pixel_(min_source_size_in_pixel), desired_size_in_pixel_(desired_size_in_pixel), - callback_(callback), + raw_bitmap_callback_(raw_bitmap_callback), + image_callback_(image_callback), background_task_runner_(background_task_runner), - tracker_(tracker) { -} + tracker_(tracker), + fallback_icon_style_( + base::MakeUnique<favicon_base::FallbackIconStyle>()) {} LargeIconWorker::~LargeIconWorker() { } void LargeIconWorker::OnIconLookupComplete( - const favicon_base::FaviconRawBitmapResult& bitmap_result) { - bitmap_result_ = bitmap_result; + const favicon_base::FaviconRawBitmapResult& db_result) { tracker_->PostTaskAndReply( background_task_runner_.get(), FROM_HERE, - base::Bind(&LargeIconWorker::ProcessIconOnBackgroundThread, this), + base::Bind(&ProcessIconOnBackgroundThread, db_result, + min_source_size_in_pixel_, desired_size_in_pixel_, + raw_bitmap_callback_ ? &raw_bitmap_result_ : nullptr, + image_callback_ ? &bitmap_result_ : nullptr, + fallback_icon_style_.get()), base::Bind(&LargeIconWorker::OnIconProcessingComplete, this)); } -void LargeIconWorker::ProcessIconOnBackgroundThread() { - favicon_base::FaviconRawBitmapResult resized_bitmap_result; - if (ResizeLargeIconOnBackgroundThreadIfValid(&resized_bitmap_result)) { - result_.reset( - new favicon_base::LargeIconResult(resized_bitmap_result)); - } else { - // Failed to resize |bitmap_result_|, so compute fallback icon style. - std::unique_ptr<favicon_base::FallbackIconStyle> fallback_icon_style( - new favicon_base::FallbackIconStyle()); - if (bitmap_result_.is_valid()) { - favicon_base::SetDominantColorAsBackground( - bitmap_result_.bitmap_data, fallback_icon_style.get()); +void LargeIconWorker::OnIconProcessingComplete() { + // If |raw_bitmap_callback_| is provided, return the raw result. + if (raw_bitmap_callback_) { + if (raw_bitmap_result_.is_valid()) { + raw_bitmap_callback_.Run( + favicon_base::LargeIconResult(raw_bitmap_result_)); + return; } - result_.reset( - new favicon_base::LargeIconResult(fallback_icon_style.release())); + raw_bitmap_callback_.Run( + favicon_base::LargeIconResult(fallback_icon_style_.release())); + return; } -} - -bool LargeIconWorker::ResizeLargeIconOnBackgroundThreadIfValid( - favicon_base::FaviconRawBitmapResult* resized_bitmap_result) { - // Require bitmap to be valid and square. - if (!bitmap_result_.is_valid() || - bitmap_result_.pixel_size.width() != bitmap_result_.pixel_size.height()) - return false; - // Require bitmap to be large enough. It's square, so just check width. - if (bitmap_result_.pixel_size.width() < min_source_size_in_pixel_) - return false; - - *resized_bitmap_result = bitmap_result_; - - // Special case: Can use |bitmap_result_| as is. - if (desired_size_in_pixel_ == 0 || - bitmap_result_.pixel_size.width() == desired_size_in_pixel_) - return true; + if (!bitmap_result_.isNull()) { + image_callback_.Run(favicon_base::LargeIconImageResult( + gfx::Image::CreateFrom1xBitmap(bitmap_result_))); + return; + } + image_callback_.Run( + favicon_base::LargeIconImageResult(fallback_icon_style_.release())); +} - // Resize bitmap: decode PNG, resize, and re-encode PNG. - SkBitmap decoded_bitmap; - if (!gfx::PNGCodec::Decode(bitmap_result_.bitmap_data->front(), - bitmap_result_.bitmap_data->size(), &decoded_bitmap)) - return false; +void ReportDownloadedSize(int size) { + UMA_HISTOGRAM_COUNTS_1000("Favicons.LargeIconService.DownloadedSize", size); +} - SkBitmap resized_bitmap = skia::ImageOperations::Resize( - decoded_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, - desired_size_in_pixel_, desired_size_in_pixel_); +void OnFetchIconFromGoogleServerComplete( + FaviconService* favicon_service, + const GURL& page_url, + const base::Callback<void(bool success)>& callback, + const std::string& server_request_url, + const gfx::Image& image, + const image_fetcher::RequestMetadata& metadata) { + if (image.IsEmpty()) { + DLOG(WARNING) << "large icon server fetch empty " << server_request_url; + favicon_service->UnableToDownloadFavicon(GURL(server_request_url)); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + base::Bind(callback, false)); + ReportDownloadedSize(0); + return; + } - std::vector<unsigned char> bitmap_data; - if (!gfx::PNGCodec::EncodeBGRASkBitmap(resized_bitmap, false, &bitmap_data)) - return false; + ReportDownloadedSize(image.Width()); - resized_bitmap_result->pixel_size = - gfx::Size(desired_size_in_pixel_, desired_size_in_pixel_); - resized_bitmap_result->bitmap_data = - base::RefCountedBytes::TakeVector(&bitmap_data); - return true; -} + // If given, use the original favicon URL from Content-Location http header. + // Otherwise, use the request URL as fallback. + std::string original_icon_url = metadata.content_location_header; + if (original_icon_url.empty()) { + original_icon_url = server_request_url; + } -void LargeIconWorker::OnIconProcessingComplete() { - callback_.Run(*result_); + // Write fetched icons to FaviconService's cache, but only if no icon was + // available (clients are encouraged to do this in advance, but meanwhile + // something else could've been written). By marking the icons initially + // expired (out-of-date), they will be refetched when we visit the original + // page any time in the future. + favicon_service->SetLastResortFavicons(page_url, GURL(original_icon_url), + favicon_base::IconType::TOUCH_ICON, + image, callback); } } // namespace -namespace favicon { - LargeIconService::LargeIconService( FaviconService* favicon_service, - const scoped_refptr<base::TaskRunner>& background_task_runner) + const scoped_refptr<base::TaskRunner>& background_task_runner, + std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher) : favicon_service_(favicon_service), - background_task_runner_(background_task_runner) { + background_task_runner_(background_task_runner), + image_fetcher_(std::move(image_fetcher)) { large_icon_types_.push_back(favicon_base::IconType::FAVICON); large_icon_types_.push_back(favicon_base::IconType::TOUCH_ICON); large_icon_types_.push_back(favicon_base::IconType::TOUCH_PRECOMPOSED_ICON); @@ -178,18 +293,72 @@ LargeIconService::~LargeIconService() { } base::CancelableTaskTracker::TaskId - LargeIconService::GetLargeIconOrFallbackStyle( +LargeIconService::GetLargeIconOrFallbackStyle( + const GURL& page_url, + int min_source_size_in_pixel, + int desired_size_in_pixel, + const favicon_base::LargeIconCallback& raw_bitmap_callback, + base::CancelableTaskTracker* tracker) { + return GetLargeIconOrFallbackStyleImpl( + page_url, min_source_size_in_pixel, desired_size_in_pixel, + raw_bitmap_callback, favicon_base::LargeIconImageCallback(), tracker); +} + +base::CancelableTaskTracker::TaskId +LargeIconService::GetLargeIconImageOrFallbackStyle( + const GURL& page_url, + int min_source_size_in_pixel, + int desired_size_in_pixel, + const favicon_base::LargeIconImageCallback& image_callback, + base::CancelableTaskTracker* tracker) { + return GetLargeIconOrFallbackStyleImpl( + page_url, min_source_size_in_pixel, desired_size_in_pixel, + favicon_base::LargeIconCallback(), image_callback, tracker); +} + +void LargeIconService:: + GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache( const GURL& page_url, int min_source_size_in_pixel, - int desired_size_in_pixel, - const favicon_base::LargeIconCallback& callback, - base::CancelableTaskTracker* tracker) { + const base::Callback<void(bool success)>& callback) { + DCHECK_LE(0, min_source_size_in_pixel); + + const GURL trimmed_page_url = TrimPageUrlForGoogleServer(page_url); + const GURL server_request_url = GetRequestUrlForGoogleServerV2( + trimmed_page_url, min_source_size_in_pixel); + + // Do not download if the URL is invalid after trimming, or there is a + // previous cache miss recorded for |server_request_url|. + if (!server_request_url.is_valid() || !trimmed_page_url.is_valid() || + !image_fetcher_ || + favicon_service_->WasUnableToDownloadFavicon(server_request_url)) { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + base::Bind(callback, false)); + return; + } + + image_fetcher_->SetDataUseServiceName( + data_use_measurement::DataUseUserData::LARGE_ICON_SERVICE); + image_fetcher_->StartOrQueueNetworkRequest( + server_request_url.spec(), server_request_url, + base::Bind(&OnFetchIconFromGoogleServerComplete, favicon_service_, + page_url, callback)); +} + +base::CancelableTaskTracker::TaskId +LargeIconService::GetLargeIconOrFallbackStyleImpl( + const GURL& page_url, + int min_source_size_in_pixel, + int desired_size_in_pixel, + const favicon_base::LargeIconCallback& raw_bitmap_callback, + const favicon_base::LargeIconImageCallback& image_callback, + base::CancelableTaskTracker* tracker) { DCHECK_LE(1, min_source_size_in_pixel); DCHECK_LE(0, desired_size_in_pixel); - scoped_refptr<LargeIconWorker> worker = - new LargeIconWorker(min_source_size_in_pixel, desired_size_in_pixel, - callback, background_task_runner_, tracker); + scoped_refptr<LargeIconWorker> worker = new LargeIconWorker( + min_source_size_in_pixel, desired_size_in_pixel, raw_bitmap_callback, + image_callback, background_task_runner_, tracker); // TODO(beaudoin): For now this is just a wrapper around // GetLargestRawFaviconForPageURL. Add the logic required to select the best @@ -197,8 +366,7 @@ base::CancelableTaskTracker::TaskId // a large icon is known but its bitmap is not available. return favicon_service_->GetLargestRawFaviconForPageURL( page_url, large_icon_types_, min_source_size_in_pixel, - base::Bind(&LargeIconWorker::OnIconLookupComplete, worker), - tracker); + base::Bind(&LargeIconWorker::OnIconLookupComplete, worker), tracker); } } // namespace favicon |