blob: c4be67b3bfc344f18a60b91842a747cf14f97956 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/prediction_service/prediction_service.h"
#include <cmath>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/types/optional_ref.h"
#include "components/permissions/features.h"
#include "components/permissions/prediction_service/prediction_common.h"
#include "components/permissions/prediction_service/prediction_request_features.h"
#include "components/permissions/prediction_service/prediction_service_messages.pb.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace {
constexpr base::TimeDelta kURLLookupTimeout = base::Seconds(2);
net::NetworkTrafficAnnotationTag GetTrafficAnnotationTag() {
return net::DefineNetworkTrafficAnnotation("permission_predictions", R"(
semantics {
sender: "Web Permission Perdictions"
description:
"A request to the Web Permission Predictions Service. The service will "
"attempt to predict the likelihood that the user would grant this "
"permission. Based on this prediction Chrome might decide to present "
"the user with a different UI; a less intrusive one."
trigger:
"A permission prompt is about to be shown to the user, and the user "
"has opted into Safe Browsing."
data:
"User stats helpful for attempting to predict the user's likelihood "
"of granting the permission: the permission type, the presence of a "
"user gesture, the user's OS, average deny/grant/ignore/dismiss rates "
"and total prompts shown, both for the specific permission type and "
"overall for all permission types."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"This can be disabled by disabling Safe Browsing by going to "
"Settings and then to the Security sub-menu."
chrome_policy {
SafeBrowsingProtectionLevel {
SafeBrowsingProtectionLevel: 0
}
}
})");
}
} // namespace
namespace permissions {
PredictionService::PredictionService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(url_loader_factory) {}
PredictionService::~PredictionService() = default;
void PredictionService::StartLookup(const PredictionRequestFeatures& entity,
LookupRequestCallback request_callback,
LookupResponseCallback response_callback) {
auto request = GetResourceRequest();
auto proto_request = GetPredictionRequestProto(entity);
std::string request_data;
proto_request->SerializeToString(&request_data);
SendRequestInternal(std::move(request), request_data, entity,
std::move(response_callback));
if (request_callback)
std::move(request_callback).Run(std::move(proto_request), std::string());
}
// static
const GURL PredictionService::GetPredictionServiceUrl(
bool recalculate_for_testing) {
static base::NoDestructor<GURL> default_prediction_service_url{
kDefaultPredictionServiceUrl};
static base::NoDestructor<GURL> command_line_url_override{
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kDefaultPredictionServiceUrlSwitchKey)};
// To facilitate tests that want to exercise various url building logic,
// reinitialize the static variables if this flag is set.
if (recalculate_for_testing) {
*command_line_url_override =
GURL(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kDefaultPredictionServiceUrlSwitchKey));
}
if (command_line_url_override->is_valid())
return *command_line_url_override;
return *default_prediction_service_url;
}
std::unique_ptr<network::ResourceRequest>
PredictionService::GetResourceRequest() {
auto request = std::make_unique<network::ResourceRequest>();
request->url =
prediction_service_url_override_.is_empty()
? GetPredictionServiceUrl(recalculate_service_url_every_time)
: prediction_service_url_override_;
request->load_flags = net::LOAD_DISABLE_CACHE;
request->method = "POST";
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
return request;
}
void PredictionService::SendRequestInternal(
std::unique_ptr<network::ResourceRequest> request,
const std::string& request_data,
const PredictionRequestFeatures& entity,
LookupResponseCallback response_callback) {
std::unique_ptr<network::SimpleURLLoader> owned_loader =
network::SimpleURLLoader::Create(std::move(request),
GetTrafficAnnotationTag());
owned_loader->AttachStringForUpload(request_data, "application/x-protobuf");
owned_loader->SetTimeoutDuration(kURLLookupTimeout);
owned_loader->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&PredictionService::OnURLLoaderComplete,
weak_factory_.GetWeakPtr(), entity, owned_loader.get(),
base::TimeTicks::Now()),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
pending_requests_[std::move(owned_loader)] = std::move(response_callback);
}
void PredictionService::OnURLLoaderComplete(
const PredictionRequestFeatures& entity,
network::SimpleURLLoader* loader,
base::TimeTicks request_start_time,
std::optional<std::string> response_body) {
for (auto& request : pending_requests_) {
if (request.first.get() == loader) {
auto prediction_response =
CreatePredictionsResponse(loader, response_body);
if (request.second) {
std::optional<GeneratePredictionsResponse> response;
if (prediction_response == nullptr) {
response = std::nullopt;
} else {
response = *prediction_response;
}
bool lookup_success = prediction_response != nullptr;
std::move(request.second)
.Run(lookup_success, /*Response from cache=*/false, response);
}
pending_requests_.erase(request.first);
return;
}
}
NOTREACHED() << "Unexpected loader callback.";
}
std::unique_ptr<GeneratePredictionsResponse>
PredictionService::CreatePredictionsResponse(
network::SimpleURLLoader* loader,
base::optional_ref<std::string> response_body) {
if (!response_body || loader->NetError() != net::OK ||
loader->ResponseInfo()->headers->response_code() != net::HTTP_OK) {
return nullptr;
}
auto predictions_response = std::make_unique<GeneratePredictionsResponse>();
if (!predictions_response->ParseFromString(*response_body)) {
return nullptr;
}
return predictions_response;
}
} // namespace permissions