910e62b5创建于 1月15日历史提交
// 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 <optional>
#include <string>

#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/lens/lens_url_utils.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_, /*is_contextual_tasks=*/false);
    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_, /*is_contextual_tasks=*/false);
    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_, /*is_contextual_tasks=*/false);
    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::optional<std::string> response_body) {
  std::erase_if(
      gen204_loaders_,
      [source](const std::unique_ptr<network::SimpleURLLoader>& loader) {
        return loader.get() == source;
      });
}

}  // namespace lens