910e62b5创建于 1月15日历史提交
// Copyright 2025 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/contextual_tasks/contextual_tasks_page_handler.h"

#include "base/check_deref.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/uuid.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/contextual_tasks/ai_mode_context_library_converter.h"
#include "chrome/browser/contextual_tasks/contextual_tasks_ui.h"
#include "chrome/browser/contextual_tasks/contextual_tasks_ui_service.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/webui/webui_embedding_context.h"
#include "components/application_locale_storage/application_locale_storage.h"
#include "components/contextual_tasks/public/context_decoration_params.h"
#include "components/contextual_tasks/public/contextual_task.h"
#include "components/contextual_tasks/public/contextual_task_context.h"
#include "components/contextual_tasks/public/features.h"
#include "components/contextual_tasks/public/prefs.h"
#include "components/lens/lens_url_utils.h"
#include "components/prefs/pref_service.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_ui.h"
#include "google_apis/gaia/gaia_constants.h"
#include "third_party/lens_server_proto/aim_communication.pb.h"
#include "url/gurl.h"

namespace {

constexpr char kMyActivityUrl[] = "https://myactivity.google.com/myactivity";

void OpenUrlInNewTab(content::WebUI* web_ui, const GURL& url) {
  NavigateParams params(Profile::FromWebUI(web_ui), url,
                        ui::PAGE_TRANSITION_LINK);
  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  Navigate(&params);
}

std::vector<contextual_tasks::mojom::TabPtr> TabsFromContext(
    std::unique_ptr<contextual_tasks::ContextualTaskContext> context) {
  if (!context) {
    return {};
  }

  std::vector<contextual_tasks::mojom::TabPtr> tabs;

  for (const auto& attachment : context->GetUrlAttachments()) {
    auto tab = contextual_tasks::mojom::Tab::New();
    tab->tab_id = attachment.GetTabSessionId().id();
    tab->title = base::UTF16ToUTF8(attachment.GetTitle());
    tab->url = attachment.GetURL();
    tabs.push_back(std::move(tab));
  }

  return tabs;
}

}  // namespace

ContextualTasksPageHandler::ContextualTasksPageHandler(
    mojo::PendingReceiver<contextual_tasks::mojom::PageHandler> receiver,
    ContextualTasksUI* web_ui_controller,
    contextual_tasks::ContextualTasksUiService* ui_service,
    contextual_tasks::ContextualTasksService* contextual_tasks_service)
    : receiver_(this, std::move(receiver)),
      web_ui_controller_(web_ui_controller),
      ui_service_(ui_service),
      contextual_tasks_service_(contextual_tasks_service) {
  CHECK(contextual_tasks_service_);
  contextual_tasks_service_observation_.Observe(contextual_tasks_service_);
}

ContextualTasksPageHandler::~ContextualTasksPageHandler() = default;

void ContextualTasksPageHandler::GetThreadUrl(GetThreadUrlCallback callback) {
  std::move(callback).Run(ui_service_->GetDefaultAiPageUrl());
}

void ContextualTasksPageHandler::GetUrlForTask(const base::Uuid& uuid,
                                               GetUrlForTaskCallback callback) {
  // First check if there's an initial URL.
  std::optional<GURL> initial_url = ui_service_->GetInitialUrlForTask(uuid);
  if (initial_url) {
    std::move(callback).Run(initial_url.value());
    return;
  }

  // There's a slight difference in the callback signature between the mojo
  // api (wants a reference) and the ui service (provided a moved object).
  // The latter can't provide a reference since we're not keeping it
  // long-term, hence wrapping this in a base::BindOnce.
  ui_service_->GetThreadUrlFromTaskId(
      uuid, base::BindOnce([](GetUrlForTaskCallback callback,
                              GURL url) { std::move(callback).Run(url); },
                           std::move(callback)));
}

void ContextualTasksPageHandler::SetTaskId(const base::Uuid& uuid) {
  web_ui_controller_->SetTaskId(uuid);

  // Trigger an update to the UI with the initial set of tabs for this task.
  UpdateContextForTask(uuid);
}

void ContextualTasksPageHandler::SetThreadTitle(const std::string& title) {
  web_ui_controller_->SetThreadTitle(title);
}

void ContextualTasksPageHandler::IsZeroState(const GURL& url,
                                             IsZeroStateCallback callback) {
  std::move(callback).Run(ContextualTasksUI::IsZeroState(url, ui_service_));
}

void ContextualTasksPageHandler::CloseSidePanel() {
  web_ui_controller_->CloseSidePanel();
}

void ContextualTasksPageHandler::ShowThreadHistory() {
  // Send a message to AIM to open the threads view.
  lens::ClientToAimMessage message;
  message.mutable_open_threads_view()->mutable_payload();
  PostMessageToWebview(message);
}

void ContextualTasksPageHandler::IsShownInTab(IsShownInTabCallback callback) {
  std::move(callback).Run(web_ui_controller_->IsShownInTab());
}

void ContextualTasksPageHandler::OpenMyActivityUi() {
  OpenUrlInNewTab(web_ui_controller_->web_ui(), GURL(kMyActivityUrl));
}

void ContextualTasksPageHandler::OpenHelpUi() {
  OpenUrlInNewTab(web_ui_controller_->web_ui(),
                  GURL(contextual_tasks::GetContextualTasksHelpUrl()));
}

void ContextualTasksPageHandler::OpenOnboardingHelpUi() {
  OpenUrlInNewTab(
      web_ui_controller_->web_ui(),
      GURL(contextual_tasks::GetContextualTasksOnboardingTooltipHelpUrl()));
}

void ContextualTasksPageHandler::MoveTaskUiToNewTab() {
  auto* browser = web_ui_controller_->GetBrowser();
  const auto& task_id = web_ui_controller_->GetTaskId();
  if (!task_id.has_value()) {
    LOG(ERROR) << "Attempted to open in new tab with no valid task ID.";
    return;
  }

  ui_service_->MoveTaskUiToNewTab(task_id.value(), browser,
                                  web_ui_controller_->GetInnerFrameUrl());
}

void ContextualTasksPageHandler::OnTabClickedFromSourcesMenu(int32_t tab_id,
                                                             const GURL& url) {
  if (ui_service_) {
    ui_service_->OnTabClickedFromSourcesMenu(
        tab_id, url,
        webui::GetBrowserWindowInterface(
            web_ui_controller_->web_ui()->GetWebContents()));
  }
}

void ContextualTasksPageHandler::OnWebviewMessage(
    const std::vector<uint8_t>& message) {
  lens::AimToClientMessage aim_to_client_message;
  if (!aim_to_client_message.ParseFromArray(message.data(), message.size())) {
    return;
  }

  if (aim_to_client_message.has_handshake_response()) {
    web_ui_controller_->page()->OnHandshakeComplete();
    web_ui_controller_->OnSidePanelStateChanged();
  } else if (aim_to_client_message.has_hide_input()) {
    web_ui_controller_->page()->HideInput();
  } else if (aim_to_client_message.has_restore_input()) {
    web_ui_controller_->page()->RestoreInput();
  } else if (aim_to_client_message.has_enter_basic_mode()) {
    web_ui_controller_->page()->HideInput();
  } else if (aim_to_client_message.has_exit_basic_mode()) {
    web_ui_controller_->page()->RestoreInput();
  } else if (aim_to_client_message.has_update_thread_context_library()) {
    OnReceivedUpdatedThreadContextLibrary(
        aim_to_client_message.update_thread_context_library());
  }
}

void ContextualTasksPageHandler::GetCommonSearchParams(
    bool is_dark_mode,
    bool is_side_panel,
    GetCommonSearchParamsCallback callback) {
  // The server is not yet ready to adapt the side panel UI unless the gsc=2
  // param is set. So force side panel mode if the temporary feature flag is
  // enabled.
  if (contextual_tasks::ShouldForceGscInTabMode()) {
    is_side_panel = true;
  }

  std::string country_code =
      g_browser_process->GetFeatures()->application_locale_storage()->Get();

  if (contextual_tasks::ShouldForceCountryCodeUS()) {
    country_code = "US";
  }

  auto params = lens::GetCommonSearchParametersMap(country_code, is_dark_mode,
                                                   is_side_panel);
  if (contextual_tasks::ShouldForceCountryCodeUS()) {
    params["gl"] = "us";
  }
  std::move(callback).Run(
      base::flat_map<std::string, std::string>(params.begin(), params.end()));
}

void ContextualTasksPageHandler::OnboardingTooltipDismissed() {
  if (!web_ui_controller_->web_ui()) {
    return;
  }

  Profile* profile = Profile::FromWebUI(web_ui_controller_->web_ui());
  if (!profile) {
    return;
  }

  PrefService* prefs = profile->GetPrefs();
  if (!prefs) {
    return;
  }

  int count = prefs->GetInteger(
      contextual_tasks::kContextualTasksOnboardingTooltipDismissedCount);
  prefs->SetInteger(
      contextual_tasks::kContextualTasksOnboardingTooltipDismissedCount,
      count + 1);
}

void ContextualTasksPageHandler::PostMessageToWebview(
    const lens::ClientToAimMessage& message) {
  DCHECK(web_ui_controller_->page());
  if (!web_ui_controller_->page()) {
    return;
  }

  const size_t size = message.ByteSizeLong();
  if (size == 0) {
    LOG(WARNING) << "PostMessageToWebview called with an empty message.";
    return;
  }
  std::vector<uint8_t> serialized_message(size);
  if (!message.SerializeToArray(&serialized_message[0], size)) {
    LOG(ERROR) << "Failed to serialize ClientToAimMessage.";
    return;
  }

  web_ui_controller_->page()->PostMessageToWebview(serialized_message);
}

void ContextualTasksPageHandler::OnTaskUpdated(
    const contextual_tasks::ContextualTask& task,
    contextual_tasks::ContextualTasksService::TriggerSource source) {
  if (!web_ui_controller_->page()) {
    return;
  }

  const auto& current_task_id = web_ui_controller_->GetTaskId();
  if (current_task_id != task.GetTaskId()) {
    return;
  }

  UpdateContextForTask(task.GetTaskId());
}

void ContextualTasksPageHandler::UpdateContextForTask(
    const base::Uuid& task_id) {
  if (!base::FeatureList::IsEnabled(
          contextual_tasks::kContextualTasksContextLibrary)) {
    web_ui_controller_->page()->OnContextUpdated({});
    return;
  }
  contextual_tasks_service_->GetContextForTask(
      task_id, {contextual_tasks::ContextualTaskContextSource::kTabStrip},
      std::make_unique<contextual_tasks::ContextDecorationParams>(),
      base::BindOnce(
          [](base::WeakPtr<ContextualTasksPageHandler> self,
             std::unique_ptr<contextual_tasks::ContextualTaskContext> context) {
            if (self && self->web_ui_controller_->page()) {
              self->web_ui_controller_->page()->OnContextUpdated(
                  TabsFromContext(std::move(context)));
            }
          },
          weak_ptr_factory_.GetWeakPtr()));
}

void ContextualTasksPageHandler::OnReceivedUpdatedThreadContextLibrary(
    const lens::UpdateThreadContextLibrary& message) {
  if (!base::FeatureList::IsEnabled(
          contextual_tasks::kContextualTasksContextLibrary)) {
    return;
  }
  const auto& task_id = web_ui_controller_->GetTaskId();
  if (!task_id.has_value()) {
    return;
  }

  contextual_search::ContextualSearchSessionHandle* handle =
      web_ui_controller_->GetOrCreateContextualSessionHandle();

  std::vector<contextual_search::FileInfo> submitted_context;
  if (handle) {
    submitted_context = handle->GetSubmittedContextFileInfos();
    // Now that we have extracted the submitted contexts and are ready to update
    // the context in the ContextualTask, we can clear out the submitted context
    // from the ContextualSearchSessionHandle.
    handle->ClearSubmittedContextTokens();
  }

  std::vector<contextual_tasks::UrlResource> committed_context =
      contextual_tasks::ConvertAiModeContextToUrlResources(message,
                                                           submitted_context);
  contextual_tasks_service_->SetUrlResourcesFromServer(*task_id,
                                                       committed_context);
}