blob: b25c589694ae3910807d06bb92b429c29946aae6 [file] [log] [blame]
// Copyright 2024 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/ui/lens/lens_overlay_gen204_controller.h"
#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/format_macros.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/lens/lens_overlay_url_builder.h"
#include "components/base32/base32.h"
#include "components/lens/lens_features.h"
#include "components/metrics_services_manager/metrics_services_manager.h"
#include "components/search_engines/template_url_service.h"
#include "net/base/url_util.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 "third_party/lens_server_proto/lens_overlay_request_id.pb.h"
namespace lens {
namespace {
using LatencyType = LensOverlayGen204Controller::LatencyType;
constexpr int kMaxDownloadBytes = 1024 * 1024;
// Task completion ids.
constexpr int kCopyAsImageTaskCompletionID = 233325;
constexpr int kCopyTextTaskCompletionID = 198153;
constexpr int kSaveAsImageTaskCompletionID = 233326;
constexpr int kSelectTextTaskCompletionID = 198157;
constexpr int kTranslateTaskCompletionID = 198158;
// Semantic event ids.
constexpr int kTextGleamsViewStartSemanticEventID = 234181;
constexpr int kTextGleamsViewEndSemanticEventID = 234180;
// Query parameter keys.
constexpr char kEncodedAnalyticsIdParameter[] = "cad";
constexpr char kEncodedRequestIdParameter[] = "vsrid";
constexpr char kGen204IdentifierQueryParameter[] = "plla";
constexpr char kLatencyRequestTypeQueryParameter[] = "rt";
constexpr char kVisualInputTypeQueryParameter[] = "vit";
// Event id param used for both semantic events and task completions.
constexpr char kEventIdParameter[] = "rcid";
// Request type parameter values.
constexpr char kFullPageObjectsFetchLatencyId[] = "fpof";
constexpr char kFullPageTranslateFetchLatencyId[] = "fptf";
constexpr char kPageContentUploadLatencyId[] = "pcu";
constexpr char kPartialPageContentUploadLatencyId[] = "ppcu";
constexpr char kInteractionFetchLatencyId[] = "lif";
constexpr char kFetchStickyClusterInfoLatencyId[] = "sctr";
constexpr char kInvocationToInitialClusterInfoRequestLatencyId[] = "cstcirs";
constexpr char kInvocationToInitialFullObjectsRequestSentLatencyId[] =
"cstiors";
constexpr char kInvocationToInitialFullObjectsResponseReceivedLatencyId[] =
"cstiorr";
constexpr char kInvocationToInitialInteractionRequestLatencyId[] = "cstiirs";
constexpr char kInvocationToInitialPageContentRequestLatencyId[] = "cstipcurs";
constexpr char kInvocationToInitialPartialPageContentRequestLatencyId[] =
"cstippcurs";
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotationTag =
net::DefineNetworkTrafficAnnotation("lens_overlay_gen204", R"(
semantics {
sender: "Lens"
description: "A request to the gen204 endpoint for the Lens "
"Overlay feature in Chrome."
trigger: "The user triggered a Lens Overlay Flow by entering "
"the experience via the right click menu option for "
"searching images on the page. This annotation corresponds "
"to the gen204 logging network requests sent by the Lens "
"overlay to track latency and interaction data when the "
"user is opted into metrics reporting."
data: "Timestamp and interaction data. Only the action type "
"(e.g. the user selected text) and timestamp data is sent, "
"along with basic state information from the query controller."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "hujasonx@google.com"
}
contacts {
email: "lens-chrome@google.com"
}
}
user_data {
type: USER_CONTENT
type: WEB_CONTENT
}
last_reviewed: "2024-09-24"
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature is only shown in menus by default and does "
"nothing without explicit user action. It will be disabled if "
"the user is not opted into metrics reporting, which is on by "
"default."
chrome_policy {
LensOverlaySettings {
LensOverlaySettings: 1
}
MetricsReportingEnabled{
policy_options {mode: MANDATORY}
MetricsReportingEnabled: false
}
}
}
)");
std::string LatencyIdForType(LatencyType latency_type) {
switch (latency_type) {
case LatencyType::kInvocationToInitialClusterInfoRequestSent:
return kInvocationToInitialClusterInfoRequestLatencyId;
case LatencyType::kInvocationToInitialFullPageObjectsRequestSent:
return kInvocationToInitialFullObjectsRequestSentLatencyId;
case LatencyType::kInvocationToInitialFullPageObjectsResponseReceived:
return kInvocationToInitialFullObjectsResponseReceivedLatencyId;
case LatencyType::kInvocationToInitialInteractionRequestSent:
return kInvocationToInitialInteractionRequestLatencyId;
case LatencyType::kInvocationToInitialPageContentRequestSent:
return kInvocationToInitialPageContentRequestLatencyId;
case LatencyType::kInvocationToInitialPartialPageContentRequestSent:
return kInvocationToInitialPartialPageContentRequestLatencyId;
case LatencyType::kFullPageObjectsRequestFetchLatency:
return kFullPageObjectsFetchLatencyId;
case LatencyType::kFullPageTranslateRequestFetchLatency:
return kFullPageTranslateFetchLatencyId;
case LatencyType::kInteractionRequestFetchLatency:
return kInteractionFetchLatencyId;
case LatencyType::kPageContentUploadLatency:
return kPageContentUploadLatencyId;
case LatencyType::kPartialPageContentUploadLatency:
return kPartialPageContentUploadLatencyId;
}
}
std::string EncodeRequestId(const lens::LensOverlayRequestId& request_id) {
std::string serialized_request_id;
CHECK(request_id.SerializeToString(&serialized_request_id));
std::string encoded_request_id;
base::Base64UrlEncode(serialized_request_id,
base::Base64UrlEncodePolicy::OMIT_PADDING,
&encoded_request_id);
return encoded_request_id;
}
} // namespace
LensOverlayGen204Controller::LensOverlayGen204Controller() = default;
LensOverlayGen204Controller::~LensOverlayGen204Controller() = default;
void LensOverlayGen204Controller::OnQueryFlowStart(
lens::LensOverlayInvocationSource invocation_source,
Profile* profile,
uint64_t gen204_id) {
invocation_source_ = invocation_source;
profile_ = profile;
gen204_id_ = gen204_id;
}
void LensOverlayGen204Controller::SendLatencyGen204IfEnabled(
LatencyType latency_type,
base::TimeDelta latency_duration,
std::string vit_query_param_value,
std::optional<base::TimeDelta> cluster_info_latency,
std::optional<std::string> encoded_analytics_id,
std::optional<lens::LensOverlayRequestId> request_id) {
if (profile_ && lens::features::GetLensOverlaySendLatencyGen204()) {
std::string cluster_info_latency_string =
cluster_info_latency.has_value() &&
latency_type == LatencyType::kFullPageObjectsRequestFetchLatency
? base::StringPrintf(
",%s.%s", kFetchStickyClusterInfoLatencyId,
base::NumberToString(cluster_info_latency->InMilliseconds())
.c_str())
: "";
// PRIu64 and PRId64 are macros for formatting uint64_t and int64_t
// respectively, allowing us to bypass using NumberToString.
std::string query = base::StringPrintf(
"gen_204?atyp=csi&%s=%" PRIu64 "&%s=%s.%" PRId64 "%s&s=web&%s=%s",
kGen204IdentifierQueryParameter, gen204_id_,
kLatencyRequestTypeQueryParameter,
LatencyIdForType(latency_type).c_str(),
latency_duration.InMilliseconds(), cluster_info_latency_string,
kVisualInputTypeQueryParameter, vit_query_param_value);
auto fetch_url = GURL(TemplateURLServiceFactory::GetForProfile(profile_)
->search_terms_data()
.GoogleBaseURLValue())
.Resolve(query);
fetch_url =
lens::AppendInvocationSourceParamToURL(fetch_url, invocation_source_);
if (encoded_analytics_id.has_value()) {
fetch_url = net::AppendOrReplaceQueryParameter(
fetch_url, kEncodedAnalyticsIdParameter,
encoded_analytics_id.value());
}
if (request_id.has_value()) {
fetch_url = net::AppendOrReplaceQueryParameter(
fetch_url, kEncodedRequestIdParameter,
EncodeRequestId(request_id.value()));
}
CheckMetricsConsentAndIssueGen204NetworkRequest(fetch_url);
}
}
void LensOverlayGen204Controller::SendTaskCompletionGen204IfEnabled(
std::string encoded_analytics_id,
lens::mojom::UserAction user_action,
lens::LensOverlayRequestId request_id) {
if (profile_ && lens::features::GetLensOverlaySendTaskCompletionGen204()) {
int task_id;
switch (user_action) {
case mojom::UserAction::kTextSelection:
[[fallthrough]];
case mojom::UserAction::kTranslateTextSelection:
task_id = kSelectTextTaskCompletionID;
break;
case mojom::UserAction::kCopyText:
task_id = kCopyTextTaskCompletionID;
break;
case mojom::UserAction::kTranslateText:
task_id = kTranslateTaskCompletionID;
break;
case mojom::UserAction::kCopyAsImage:
task_id = kCopyAsImageTaskCompletionID;
break;
case mojom::UserAction::kSaveAsImage:
task_id = kSaveAsImageTaskCompletionID;
break;
default:
// Other user actions should not send an associated gen204 ping.
return;
}
std::string query = base::StringPrintf(
"gen_204?uact=4&%s=%" PRIu64 "&%s=%d&%s=%s&%s=%s",
kGen204IdentifierQueryParameter, gen204_id_, kEventIdParameter, task_id,
kEncodedAnalyticsIdParameter, encoded_analytics_id.c_str(),
kEncodedRequestIdParameter, EncodeRequestId(request_id).c_str());
auto fetch_url = GURL(TemplateURLServiceFactory::GetForProfile(profile_)
->search_terms_data()
.GoogleBaseURLValue())
.Resolve(query);
fetch_url =
lens::AppendInvocationSourceParamToURL(fetch_url, invocation_source_);
CheckMetricsConsentAndIssueGen204NetworkRequest(fetch_url);
}
}
void LensOverlayGen204Controller::SendSemanticEventGen204IfEnabled(
lens::mojom::SemanticEvent event,
std::optional<lens::LensOverlayRequestId> request_id) {
if (profile_ && lens::features::GetLensOverlaySendSemanticEventGen204()) {
int event_id;
switch (event) {
case mojom::SemanticEvent::kTextGleamsViewStart:
event_id = kTextGleamsViewStartSemanticEventID;
break;
case mojom::SemanticEvent::kTextGleamsViewEnd:
event_id = kTextGleamsViewEndSemanticEventID;
break;
}
std::string query = base::StringPrintf(
"gen_204?uact=1&%s=%d&zx=%" PRId64 "&%s=%" PRIu64, kEventIdParameter,
event_id, base::Time::Now().InMillisecondsSinceUnixEpoch(),
kGen204IdentifierQueryParameter, gen204_id_);
auto fetch_url = GURL(TemplateURLServiceFactory::GetForProfile(profile_)
->search_terms_data()
.GoogleBaseURLValue())
.Resolve(query);
fetch_url =
lens::AppendInvocationSourceParamToURL(fetch_url, invocation_source_);
if (request_id.has_value()) {
fetch_url = net::AppendOrReplaceQueryParameter(
fetch_url, kEncodedRequestIdParameter,
EncodeRequestId(request_id.value()));
}
CheckMetricsConsentAndIssueGen204NetworkRequest(fetch_url);
}
}
void LensOverlayGen204Controller::OnQueryFlowEnd() {
// Send a text gleams view end event because the event trigger from webui
// will not fire when the overlay is closing. The server will dedupe
// end events.
SendSemanticEventGen204IfEnabled(mojom::SemanticEvent::kTextGleamsViewEnd,
/*request_id=*/std::nullopt);
profile_ = nullptr;
}
void LensOverlayGen204Controller::
CheckMetricsConsentAndIssueGen204NetworkRequest(GURL url) {
if (!g_browser_process->GetMetricsServicesManager()
->IsMetricsConsentGiven()) {
return;
}
auto request = std::make_unique<network::ResourceRequest>();
request->url = url;
gen204_loaders_.push_back(network::SimpleURLLoader::Create(
std::move(request), kTrafficAnnotationTag));
gen204_loaders_.back()->DownloadToString(
profile_->GetURLLoaderFactory().get(),
base::BindOnce(&LensOverlayGen204Controller::OnGen204NetworkResponse,
weak_ptr_factory_.GetWeakPtr(),
gen204_loaders_.back().get()),
kMaxDownloadBytes);
}
void LensOverlayGen204Controller::OnGen204NetworkResponse(
const network::SimpleURLLoader* source,
std::unique_ptr<std::string> response_body) {
std::erase_if(
gen204_loaders_,
[source](const std::unique_ptr<network::SimpleURLLoader>& loader) {
return loader.get() == source;
});
}
} // namespace lens