blob: 6746df4dba328984f8c424daa276008274d57966 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/autofill/autofill_image_fetcher_impl.h"
#include "chrome/browser/image_fetcher/image_fetcher_service_factory.h"
#include "chrome/browser/profiles/profile_key.h"
#include "components/autofill/core/browser/data_model/credit_card_art_image.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/payments/constants.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_tick_clock.h"
#include "components/image_fetcher/core/cached_image_fetcher.h"
#include "components/image_fetcher/core/image_fetcher_service.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace autofill {
namespace {
constexpr char kUmaClientName[] = "AutofillImageFetcher";
constexpr net::NetworkTrafficAnnotationTag kCardArtImageTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("autofill_image_fetcher_card_art_image",
R"(
semantics {
sender: "Autofill Image Fetcher"
description:
"Fetches customized card art images for credit cards stored in "
"Chrome. Images are hosted on Google static content server, "
"the data source may come from third parties (credit card issuers)."
trigger: "When new credit card data is sent to Chrome if the card "
"has a related card art image, and when the credit card data in "
"the web database is refreshed and any card art image is missing."
data: "URL of the image to be fetched."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can enable or disable this feature in Chromium settings by "
"toggling 'Credit cards and addresses using Google Payments', "
"under 'Advanced sync settings...'."
chrome_policy {
AutoFillEnabled {
policy_options {mode: MANDATORY}
AutoFillEnabled: false
}
}
})");
// The image radius value for card art images.
constexpr int kCardArtImageRadius = 3; // 3dp
// The SkAlpha value for the image grey overlay.
constexpr double kImageOverlayAlpha = 0.04; // 4%
// The border color used for card art images.
constexpr SkColor kCardArtBorderColor = SkColorSetARGB(0xFF, 0xE3, 0xE3, 0xE3);
// The stroke width of the card art border.
constexpr int kCardArtBorderStrokeWidth = 2;
// The width and length card art is resized to.
constexpr int kCardArtImageWidth = 40;
constexpr int kCardArtImageHeight = 24;
} // namespace
AutofillImageFetcherImpl::AutofillImageFetcherImpl(ProfileKey* key)
: key_(key) {}
AutofillImageFetcherImpl::~AutofillImageFetcherImpl() = default;
void AutofillImageFetcherImpl::FetchImagesForURLs(
base::span<const GURL> card_art_urls,
base::OnceCallback<void(const CardArtImageData&)> callback) {
InitializeImageFetcher();
if (!image_fetcher_) {
std::move(callback).Run({});
return;
}
// Construct a BarrierCallback and so that the inner `callback` is invoked
// only when all the images are fetched.
const auto barrier_callback =
base::BarrierCallback<std::unique_ptr<CreditCardArtImage>>(
card_art_urls.size(), std::move(callback));
for (const auto& card_art_url : card_art_urls)
FetchImageForURL(barrier_callback, card_art_url);
}
void AutofillImageFetcherImpl::OnCardArtImageFetched(
base::OnceCallback<void(std::unique_ptr<CreditCardArtImage>)>
barrier_callback,
const GURL& card_art_url,
const absl::optional<base::TimeTicks>& fetch_image_request_timestamp,
const gfx::Image& card_art_image,
const image_fetcher::RequestMetadata& metadata) {
// In case of an invalid url, `fetch_image_request_timestamp` is nullopt, and
// hence we don't report any UMA metrics.
if (fetch_image_request_timestamp.has_value()) {
AutofillMetrics::LogImageFetcherRequestLatency(
AutofillTickClock::NowTicks() - *fetch_image_request_timestamp);
}
AutofillMetrics::LogImageFetchResult(/*succeeded=*/!card_art_image.IsEmpty());
auto credit_card_art_image =
std::make_unique<CreditCardArtImage>(card_art_url, gfx::Image());
if (!card_art_image.IsEmpty()) {
if (base::FeatureList::IsEnabled(
features::kAutofillEnableNewCardArtAndNetworkImages)) {
if (card_art_url ==
"https://www.gstatic.com/autofill/virtualcard/icon/"
"capitalone_40_24.png") {
// Render Capital One asset directly. No need to calculate and add grey
// border to image.
credit_card_art_image->card_art_image = card_art_image;
} else {
// Create the outer rectange. The outer rectangle is for the
// entire image which includes the card art and additional border.
gfx::RectF outer_rect =
gfx::RectF(kCardArtImageWidth, kCardArtImageHeight);
// The inner rectangle only includes the card art. To calculate the
// inner rectangle, we need to factor the space that the border stroke
// will take up.
gfx::RectF inner_rect = gfx::RectF(
/*x=*/kCardArtBorderStrokeWidth, /*y=*/kCardArtBorderStrokeWidth,
/*width=*/kCardArtImageWidth - (kCardArtBorderStrokeWidth * 2),
/*height=*/kCardArtImageHeight - (kCardArtBorderStrokeWidth * 2));
gfx::Canvas canvas =
gfx::Canvas(gfx::Size(kCardArtImageWidth, kCardArtImageHeight),
/*image_scale=*/1.0f, /*is_opaque=*/false);
cc::PaintFlags card_art_paint;
card_art_paint.setAntiAlias(true);
// Draw card art with rounded corners in the inner rectangle.
canvas.DrawRoundRect(inner_rect, kCardArtImageRadius, card_art_paint);
canvas.DrawImageInt(
gfx::ImageSkiaOperations::CreateResizedImage(
card_art_image.AsImageSkia(),
skia::ImageOperations::RESIZE_BEST,
gfx::Size(kCardArtImageWidth, kCardArtImageHeight)),
outer_rect.x(), outer_rect.y(), card_art_paint);
// Draw border around card art using outer rectangle.
card_art_paint.setStrokeWidth(kCardArtBorderStrokeWidth);
card_art_paint.setColor(kCardArtBorderColor);
card_art_paint.setStyle(cc::PaintFlags::kStroke_Style);
canvas.DrawRoundRect(outer_rect, kCardArtImageRadius, card_art_paint);
// Add radius around entire image.
credit_card_art_image->card_art_image =
gfx::Image(gfx::ImageSkiaOperations::CreateImageWithRoundRectClip(
kCardArtImageRadius,
gfx::ImageSkia::CreateFromBitmap(canvas.GetBitmap(), 1.0f)));
}
} else {
credit_card_art_image->card_art_image =
AutofillImageFetcherImpl::ApplyGreyOverlay(card_art_image);
}
}
std::move(barrier_callback).Run(std::move(credit_card_art_image));
}
// static
gfx::Image AutofillImageFetcherImpl::ApplyGreyOverlay(const gfx::Image& image) {
// Create a solid dark grey mask for the image.
gfx::ImageSkia mask = gfx::ImageSkiaOperations::CreateColorMask(
image.AsImageSkia(), SK_ColorDKGRAY);
// Apply the mask to the original card art image with alpha set to
// `kImageOverlayAlpha`.
return gfx::Image(gfx::ImageSkiaOperations::CreateBlendedImage(
image.AsImageSkia(), mask, kImageOverlayAlpha));
}
void AutofillImageFetcherImpl::FetchImageForURL(
base::OnceCallback<void(std::unique_ptr<CreditCardArtImage>)>
barrier_callback,
const GURL& card_art_url) {
if (!card_art_url.is_valid()) {
OnCardArtImageFetched(std::move(barrier_callback), card_art_url,
absl::nullopt, gfx::Image(),
image_fetcher::RequestMetadata());
return;
}
GURL url;
// TODO(crbug.com/1313616): There is only one gstatic card art image we are
// using currently, that returns as metadata when it isn't. Remove this logic
// and append FIFE URL suffix by default when the static image is deprecated,
// and we send rich card art instead.
// Check if the image is stored in Static Content Service. If not append the
// FIFE URL option to fetch the correct image.
if (card_art_url.spec() == kCapitalOneCardArtUrl) {
url = base::FeatureList::IsEnabled(
features::kAutofillEnableNewCardArtAndNetworkImages)
? GURL(kCapitalOneLargeCardArtUrl)
: card_art_url;
} else {
// A FIFE image fetching param suffix is appended to the URL. The image
// should be center cropped and of Size(32, 20) unless the
// kAutofillEnableNewCardArtAndNetworkImages feature is enabled, in which
// case we take the image at its raw size and resize it to Size(40, 24)
// later.
url = base::FeatureList::IsEnabled(
features::kAutofillEnableNewCardArtAndNetworkImages)
? card_art_url
: GURL(card_art_url.spec() + "=w32-h20-n");
}
image_fetcher::ImageFetcherParams params(kCardArtImageTrafficAnnotation,
kUmaClientName);
image_fetcher_->FetchImage(
url,
base::BindOnce(&AutofillImageFetcherImpl::OnCardArtImageFetched,
weak_ptr_factory_.GetWeakPtr(),
std::move(barrier_callback), card_art_url,
AutofillTickClock::NowTicks()),
std::move(params));
}
void AutofillImageFetcherImpl::InitializeImageFetcher() {
if (image_fetcher_)
return;
// Lazily initialize the `image_fetcher_`, since
// ImageFetcherServiceFactory relies on the initialization of the profile, and
// when AutofillImageFetcher is created, the initialization of the profile is
// not done yet.
image_fetcher::ImageFetcherService* image_fetcher_service =
ImageFetcherServiceFactory::GetForKey(key_);
if (!image_fetcher_service)
return;
// TODO(crbug.com/1382289): Fix and change the config back to kDiskCacheOnly.
image_fetcher_ = image_fetcher_service->GetImageFetcher(
image_fetcher::ImageFetcherConfig::kNetworkOnly);
}
} // namespace autofill