blob: b07e41f7f770f7ad051da2b7ea7a0f4f7af6b517 [file] [log] [blame]
// Copyright 2018 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/image_fetcher/core/cached_image_fetcher.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/timer/elapsed_timer.h"
#include "components/image_fetcher/core/cache/image_cache.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher_metrics_reporter.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
namespace image_fetcher {
struct CachedImageFetcherRequest {
// The url to be fetched.
const GURL url;
const ImageFetcherParams params;
// Analytic events below.
// True if there was a cache hit during the fetch sequence.
bool cache_hit_before_network_request;
// The start time of the fetch sequence.
const base::Time start_time;
};
namespace {
void DataCallbackIfPresent(ImageDataFetcherCallback data_callback,
const std::string& image_data,
const image_fetcher::RequestMetadata& metadata) {
if (data_callback.is_null()) {
return;
}
std::move(data_callback).Run(image_data, metadata);
}
void ImageCallbackIfPresent(ImageFetcherCallback image_callback,
const gfx::Image& image,
const image_fetcher::RequestMetadata& metadata) {
if (image_callback.is_null()) {
return;
}
std::move(image_callback).Run(image, metadata);
}
std::string EncodeSkBitmapToPNG(const std::string& uma_client_name,
const SkBitmap& bitmap) {
std::vector<unsigned char> encoded_data;
bool result = gfx::PNGCodec::Encode(
static_cast<const unsigned char*>(bitmap.getPixels()),
gfx::PNGCodec::FORMAT_RGBA, gfx::Size(bitmap.width(), bitmap.height()),
static_cast<int>(bitmap.rowBytes()), /* discard_transparency */ false,
std::vector<gfx::PNGCodec::Comment>(), &encoded_data);
if (!result) {
ImageFetcherMetricsReporter::ReportEvent(
uma_client_name, ImageFetcherEvent::kTranscodingError);
return "";
} else {
return std::string(encoded_data.begin(), encoded_data.end());
}
}
} // namespace
CachedImageFetcher::CachedImageFetcher(ImageFetcher* image_fetcher,
scoped_refptr<ImageCache> image_cache,
bool read_only)
: image_fetcher_(image_fetcher),
image_cache_(image_cache),
read_only_(read_only),
weak_ptr_factory_(this) {
DCHECK(image_fetcher_);
DCHECK(image_cache_);
}
CachedImageFetcher::~CachedImageFetcher() = default;
ImageDecoder* CachedImageFetcher::GetImageDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return image_fetcher_->GetImageDecoder();
}
void CachedImageFetcher::FetchImageAndData(
const GURL& image_url,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
ImageFetcherParams params) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(wylieb): Inject a clock for better testability.
CachedImageFetcherRequest request = {
image_url, std::move(params),
/* cache_hit_before_network_request */ false,
/* start_time */ base::Time::Now()};
ImageFetcherMetricsReporter::ReportEvent(request.params.uma_client_name(),
ImageFetcherEvent::kImageRequest);
if (params.skip_disk_cache_read()) {
EnqueueFetchImageFromNetwork(request, std::move(image_data_callback),
std::move(image_callback));
} else {
// First, try to load the image from the cache, then try the network.
image_cache_->LoadImage(
read_only_, image_url.spec(),
base::BindOnce(&CachedImageFetcher::OnImageFetchedFromCache,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(image_data_callback),
std::move(image_callback)));
}
}
void CachedImageFetcher::OnImageFetchedFromCache(
CachedImageFetcherRequest request,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
std::string image_data) {
if (image_data.empty()) {
ImageFetcherMetricsReporter::ReportEvent(request.params.uma_client_name(),
ImageFetcherEvent::kCacheMiss);
// Fetching from the DB failed, start a network fetch.
EnqueueFetchImageFromNetwork(std::move(request),
std::move(image_data_callback),
std::move(image_callback));
} else {
DataCallbackIfPresent(std::move(image_data_callback), image_data,
RequestMetadata());
ImageFetcherMetricsReporter::ReportEvent(request.params.uma_client_name(),
ImageFetcherEvent::kCacheHit);
// Only continue with decoding if the user actually asked for an image.
if (!image_callback.is_null()) {
GetImageDecoder()->DecodeImage(
image_data, gfx::Size(),
base::BindOnce(&CachedImageFetcher::OnImageDecodedFromCache,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(image_data_callback),
std::move(image_callback)));
}
}
}
void CachedImageFetcher::OnImageDecodedFromCache(
CachedImageFetcherRequest request,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback,
const gfx::Image& image) {
if (image.IsEmpty()) {
// Upon failure, fetch from the network.
request.cache_hit_before_network_request = true;
EnqueueFetchImageFromNetwork(std::move(request),
std::move(image_data_callback),
std::move(image_callback));
ImageFetcherMetricsReporter::ReportEvent(
request.params.uma_client_name(),
ImageFetcherEvent::kCacheDecodingError);
} else {
ImageCallbackIfPresent(std::move(image_callback), image, RequestMetadata());
ImageFetcherMetricsReporter::ReportImageLoadFromCacheTime(
request.params.uma_client_name(), request.start_time);
}
}
void CachedImageFetcher::EnqueueFetchImageFromNetwork(
CachedImageFetcherRequest request,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&CachedImageFetcher::FetchImageFromNetwork,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(image_data_callback),
std::move(image_callback)));
}
void CachedImageFetcher::FetchImageFromNetwork(
CachedImageFetcherRequest request,
ImageDataFetcherCallback image_data_callback,
ImageFetcherCallback image_callback) {
const GURL& url = request.url;
ImageDataFetcherCallback wrapper_data_callback;
ImageFetcherCallback wrapper_image_callback;
bool skip_transcoding = request.params.skip_transcoding();
if (skip_transcoding) {
wrapper_data_callback =
base::BindOnce(&CachedImageFetcher::StoreImageDataWithoutTranscoding,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(image_data_callback));
} else {
// Transcode the image when its downloaded from the network.
// 1. Download the data.
// 2. Decode the data to a gfx::Image in a utility process.
// 3. Encode the data as a PNG in the browser process using base::PostTask.
// 3. Cache the result.
wrapper_data_callback = std::move(image_data_callback);
wrapper_image_callback =
base::BindOnce(&CachedImageFetcher::StoreImageDataWithTranscoding,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(image_callback));
}
image_fetcher_->FetchImageAndData(url, std::move(wrapper_data_callback),
std::move(wrapper_image_callback),
std::move(request.params));
}
void CachedImageFetcher::StoreImageDataWithoutTranscoding(
CachedImageFetcherRequest request,
ImageDataFetcherCallback image_data_callback,
const std::string& image_data,
const RequestMetadata& request_metadata) {
DataCallbackIfPresent(std::move(image_data_callback), image_data,
request_metadata);
if (image_data.empty()) {
ImageFetcherMetricsReporter::ReportEvent(request.params.uma_client_name(),
ImageFetcherEvent::kTotalFailure);
}
StoreData(std::move(request), image_data);
}
void CachedImageFetcher::StoreImageDataWithTranscoding(
CachedImageFetcherRequest request,
ImageFetcherCallback image_callback,
const gfx::Image& image,
const RequestMetadata& request_metadata) {
ImageCallbackIfPresent(std::move(image_callback), image, request_metadata);
// Report to different histograms depending upon if there was a cache hit.
if (request.cache_hit_before_network_request) {
ImageFetcherMetricsReporter::ReportImageLoadFromNetworkAfterCacheHit(
request.params.uma_client_name(), request.start_time);
} else {
ImageFetcherMetricsReporter::ReportImageLoadFromNetworkTime(
request.params.uma_client_name(), request.start_time);
}
// Copy the image data out and store it on disk.
const SkBitmap* bitmap = image.IsEmpty() ? nullptr : image.ToSkBitmap();
// If the bitmap is null or otherwise not ready, skip encoding.
if (bitmap == nullptr || bitmap->isNull() || !bitmap->readyToDraw()) {
ImageFetcherMetricsReporter::ReportEvent(request.params.uma_client_name(),
ImageFetcherEvent::kTotalFailure);
StoreData(std::move(request), "");
} else {
std::string uma_client_name = request.params.uma_client_name();
// Post a task to another thread to encode the image data downloaded.
base::PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&EncodeSkBitmapToPNG, uma_client_name, *bitmap),
base::BindOnce(&CachedImageFetcher::StoreData,
weak_ptr_factory_.GetWeakPtr(), std::move(request)));
}
}
void CachedImageFetcher::StoreData(CachedImageFetcherRequest request,
std::string image_data) {
std::string url = request.url.spec();
// If the image is empty, delete the image.
if (image_data.empty()) {
image_cache_->DeleteImage(std::move(url));
return;
}
if (!read_only_) {
image_cache_->SaveImage(std::move(url), std::move(image_data));
}
}
} // namespace image_fetcher