blob: b1d928502fca25d29ae179f752cb170e0a5f6c6f [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/fast_checkout/fast_checkout_capabilities_fetcher_impl.h"
#include <memory>
#include "base/metrics/histogram_functions.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/origin.h"
namespace {
constexpr int kMaxDownloadSizeInBytes = 10 * 1024;
constexpr char kFastCheckoutFunnelsUrl[] =
"https://www.gstatic.com/autofill/fast_checkout/funnels.binarypb";
constexpr base::TimeDelta kCacheTimeout(base::Minutes(10));
constexpr base::TimeDelta kFetchTimeout(base::Seconds(3));
constexpr char kUmaKeyCacheStateIsTriggerFormSupported[] =
"Autofill.FastCheckout.CapabilitiesFetcher."
"CacheStateForIsTriggerFormSupported";
constexpr char kUmaKeyParsingResult[] =
"Autofill.FastCheckout.CapabilitiesFetcher.ParsingResult";
constexpr char kUmaKeyResponseAndNetErrorCode[] =
"Autofill.FastCheckout.CapabilitiesFetcher.HttpResponseAndNetErrorCode";
constexpr char kUmaKeyResponseTime[] =
"Autofill.FastCheckout.CapabilitiesFetcher.ResponseTime";
} // namespace
FastCheckoutCapabilitiesFetcherImpl::FastCheckoutFunnel::FastCheckoutFunnel() =
default;
FastCheckoutCapabilitiesFetcherImpl::FastCheckoutFunnel::~FastCheckoutFunnel() =
default;
FastCheckoutCapabilitiesFetcherImpl::FastCheckoutFunnel::FastCheckoutFunnel(
const FastCheckoutFunnel&) = default;
FastCheckoutCapabilitiesFetcherImpl::FastCheckoutCapabilitiesFetcherImpl(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(url_loader_factory) {}
FastCheckoutCapabilitiesFetcherImpl::~FastCheckoutCapabilitiesFetcherImpl() =
default;
void FastCheckoutCapabilitiesFetcherImpl::FetchCapabilities() {
if (url_loader_) {
// There is an ongoing request.
return;
}
if (!IsCacheStale()) {
return;
}
cache_.clear();
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = GURL(kFastCheckoutFunnelsUrl);
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("gstatic_fast_checkout_funnels",
R"(
semantics {
sender: "Fast Checkout Tab Helper"
description:
"A binary proto string containing all funnels supported by Fast "
"Checkout."
trigger:
"When the user visits a checkout page."
data:
"The request body is empty. No user data is included."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"The user can enable or disable this feature via 'Save and fill "
"payment methods' and 'Save and fill addresses' in Chromium's "
"settings under 'Payment methods' and 'Addresses and more' "
"respectively. The feature is enabled by default."
chrome_policy {
AutofillCreditCardEnabled {
policy_options {mode: MANDATORY}
AutofillCreditCardEnabled: true
}
}
chrome_policy {
AutofillAddressEnabled {
policy_options {mode: MANDATORY}
AutofillAddressEnabled: true
}
}
})");
url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation);
url_loader_->SetTimeoutDuration(kFetchTimeout);
url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&FastCheckoutCapabilitiesFetcherImpl::OnFetchComplete,
base::Unretained(this), base::TimeTicks::Now()),
kMaxDownloadSizeInBytes);
}
bool FastCheckoutCapabilitiesFetcherImpl::IsCacheStale() const {
return last_fetch_timestamp_.is_null() ||
base::TimeTicks::Now() - last_fetch_timestamp_ >= kCacheTimeout;
}
void FastCheckoutCapabilitiesFetcherImpl::OnFetchComplete(
base::TimeTicks start_time,
std::unique_ptr<std::string> response_body) {
base::UmaHistogramTimes(kUmaKeyResponseTime,
base::TimeTicks::Now() - start_time);
int net_error = url_loader_->NetError();
bool report_http_response_code =
(net_error == net::OK ||
net_error == net::ERR_HTTP_RESPONSE_CODE_FAILURE) &&
url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers;
base::UmaHistogramSparse(
kUmaKeyResponseAndNetErrorCode,
report_http_response_code
? url_loader_->ResponseInfo()->headers->response_code()
: net_error);
// Reset `url_loader_` so that another request could be made.
url_loader_.reset();
last_fetch_timestamp_ = base::TimeTicks::Now();
if (net_error != net::OK) {
return;
}
if (!response_body) {
base::UmaHistogramEnumeration(kUmaKeyParsingResult,
ParsingResult::kNullResponse);
return;
}
::fast_checkout::FastCheckoutFunnels funnels;
if (!funnels.ParseFromString(*response_body)) {
base::UmaHistogramEnumeration(kUmaKeyParsingResult,
ParsingResult::kParsingError);
return;
}
base::UmaHistogramEnumeration(kUmaKeyParsingResult, ParsingResult::kSuccess);
for (const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel&
funnel_proto : funnels.funnels()) {
AddFunnelToCache(funnel_proto);
}
}
void FastCheckoutCapabilitiesFetcherImpl::AddFunnelToCache(
const ::fast_checkout::FastCheckoutFunnels_FastCheckoutFunnel&
funnel_proto) {
// There has to be at least one trigger form signature for a funnel. Otherwise
// a run could never be triggered successfully.
if (funnel_proto.trigger().empty()) {
return;
}
FastCheckoutFunnel funnel;
for (uint64_t form_signature : funnel_proto.trigger()) {
funnel.trigger.emplace(form_signature);
}
for (uint64_t form_signature : funnel_proto.fill()) {
funnel.fill.emplace(form_signature);
}
for (const std::string& domain : funnel_proto.domains()) {
GURL url = GURL(domain);
if (url.is_valid() && url.SchemeIsHTTPOrHTTPS()) {
cache_.emplace(url::Origin::Create(url), funnel);
}
}
}
bool FastCheckoutCapabilitiesFetcherImpl::IsTriggerFormSupported(
const url::Origin& origin,
autofill::FormSignature form_signature) {
if (!cache_.contains(origin)) {
base::UmaHistogramEnumeration(
kUmaKeyCacheStateIsTriggerFormSupported,
url_loader_ ? CacheStateForIsTriggerFormSupported::kFetchOngoing
: CacheStateForIsTriggerFormSupported::kEntryNotAvailable);
return false;
}
bool is_supported = cache_.at(origin).trigger.contains(form_signature);
base::UmaHistogramEnumeration(
kUmaKeyCacheStateIsTriggerFormSupported,
is_supported
? CacheStateForIsTriggerFormSupported::kEntryAvailableAndFormSupported
: CacheStateForIsTriggerFormSupported::
kEntryAvailableAndFormNotSupported);
return is_supported;
}
base::flat_set<autofill::FormSignature>
FastCheckoutCapabilitiesFetcherImpl::GetFormsToFill(const url::Origin& origin) {
if (!cache_.contains(origin)) {
return {};
}
const FastCheckoutFunnel& funnel = cache_.at(origin);
// All `FastCheckoutFunnel::trigger` and `FastCheckoutFunnel::fill` forms
// should be attempted to be filled, in any order. For that reason, merge the
// two sets into one set (`forms_to_fill`) and return it.
base::flat_set<autofill::FormSignature> forms_to_fill = funnel.trigger;
forms_to_fill.insert(funnel.fill.begin(), funnel.fill.end());
return forms_to_fill;
}