blob: a107603e482dda877079b01e4abea349ec6855c3 [file]
// Copyright 2026 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_extension_handler.h"
#include "build/build_config.h"
#include "chrome/browser/contextual_search/contextual_search_service_factory.h"
#include "chrome/browser/contextual_search/contextual_search_web_contents_helper.h"
#include "chrome/browser/contextual_tasks/contextual_tasks_utils.h"
#include "chrome/browser/contextual_tasks/contextual_tasks_web_contents_user_data.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_list/tab_list_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/webui/webui_embedding_context.h"
#include "chrome/common/webui_url_constants.h"
#include "components/contextual_search/contextual_search_service.h"
#include "components/contextual_search/contextual_search_session_handle.h"
#include "components/contextual_search/input_state_model.h"
#include "components/contextual_tasks/public/features.h"
#include "components/lens/lens_overlay_invocation_source.h"
#include "components/omnibox/common/input_state.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/base/proto_wrapper.h"
#include "third_party/lens_server_proto/aim_communication.pb.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/lens/lens_search_controller.h"
#endif
DOCUMENT_USER_DATA_KEY_IMPL(ContextualTasksExtensionHandler);
ContextualTasksExtensionHandler::ContextualTasksExtensionHandler(
content::RenderFrameHost* rfh)
: content::DocumentUserData<ContextualTasksExtensionHandler>(rfh) {}
ContextualTasksExtensionHandler::~ContextualTasksExtensionHandler() = default;
void ContextualTasksExtensionHandler::OnPermissionPromptChanged(
bool is_showing,
const gfx::Size& prompt_size) {
if (searchbox_page_) {
searchbox_page_->OnPermissionPromptChanged(is_showing, prompt_size);
}
}
void ContextualTasksExtensionHandler::BindComposeboxFactory(
mojo::PendingReceiver<composebox::mojom::PageHandlerFactory> receiver) {
composebox_factory_receiver_.reset();
composebox_factory_receiver_.Bind(std::move(receiver));
}
// composebox::mojom::PageHandlerFactory:
void ContextualTasksExtensionHandler::CreatePageHandler(
mojo::PendingReceiver<composebox::mojom::PageHandler> receiver,
mojo::PendingRemote<searchbox::mojom::Page> searchbox_page,
mojo::PendingReceiver<searchbox::mojom::PageHandler> searchbox_handler) {
composebox_handler_receiver_.reset();
composebox_handler_receiver_.Bind(std::move(receiver));
searchbox_page_.reset();
searchbox_page_.Bind(std::move(searchbox_page));
searchbox_handler_receiver_.reset();
searchbox_handler_receiver_.Bind(std::move(searchbox_handler));
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host());
if (web_contents) {
PermissionPromptObserver::CreateForWebContents(web_contents);
if (auto* observer =
PermissionPromptObserver::FromWebContents(web_contents)) {
permission_prompt_observation_.Reset();
permission_prompt_observation_.Observe(observer);
}
}
InitializeInputStateModel();
}
void ContextualTasksExtensionHandler::BindContextualTasksFactory(
mojo::PendingReceiver<contextual_tasks::mojom::ExtensionPageHandlerFactory>
receiver) {
contextual_tasks_factory_receiver_.reset();
contextual_tasks_factory_receiver_.Bind(std::move(receiver));
}
// contextual_tasks::mojom::ExtensionPageHandlerFactory:
void ContextualTasksExtensionHandler::CreateExtensionPageHandler(
mojo::PendingRemote<contextual_tasks::mojom::ExtensionPage> page,
mojo::PendingReceiver<contextual_tasks::mojom::ExtensionPageHandler>
receiver) {
contextual_tasks_handler_receiver_.reset();
contextual_tasks_handler_receiver_.Bind(std::move(receiver));
contextual_tasks_page_.reset();
contextual_tasks_page_.Bind(std::move(page));
}
// contextual_tasks::mojom::ExtensionPageHandler:
void ContextualTasksExtensionHandler::SetTaskId(const base::Uuid& uuid) {
task_id_ = uuid;
}
void ContextualTasksExtensionHandler::OnWebviewMessage(
const std::vector<uint8_t>& message) {
if (!contextual_tasks_page_.is_bound()) {
return;
}
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()) {
contextual_tasks_page_->OnHandshakeComplete();
} else if (aim_to_client_message.has_hide_input()) {
contextual_tasks_page_->HideInput();
} else if (aim_to_client_message.has_restore_input()) {
contextual_tasks_page_->RestoreInput();
} else if (aim_to_client_message.has_enter_basic_mode()) {
contextual_tasks_page_->EnterBasicMode();
} else if (aim_to_client_message.has_exit_basic_mode()) {
contextual_tasks_page_->ExitBasicMode();
} else if (aim_to_client_message.has_lock_input()) {
contextual_tasks_page_->LockInput();
} else if (aim_to_client_message.has_unlock_input()) {
contextual_tasks_page_->UnlockInput();
}
}
void ContextualTasksExtensionHandler::GetHandshakeMessage(
GetHandshakeMessageCallback callback) {
std::move(callback).Run(
mojo_base::ProtoWrapper(contextual_tasks::GetHandshakeMessageProto()));
}
// composebox::mojom::PageHandler stubs:
void ContextualTasksExtensionHandler::FocusChanged(bool focused) {}
void ContextualTasksExtensionHandler::StartPlatformVoiceRecognition() {}
void ContextualTasksExtensionHandler::HandleLensButtonClick() {}
void ContextualTasksExtensionHandler::HandleFileUpload(bool is_image) {}
void ContextualTasksExtensionHandler::NavigateUrl(const GURL& url) {}
void ContextualTasksExtensionHandler::CloseLensOverlayFromWebUI(
composebox::mojom::LensOverlayDismissalSource dismissal_source) {}
void ContextualTasksExtensionHandler::SetSmartTabSharingActive(bool active) {}
void ContextualTasksExtensionHandler::GetSmartTabSharingActive(
GetSmartTabSharingActiveCallback callback) {
std::move(callback).Run(false);
}
void ContextualTasksExtensionHandler::
NotifyComposeboxQuerySubmittedWithContext() {}
void ContextualTasksExtensionHandler::CanShowNextboxAnimation(
CanShowNextboxAnimationCallback callback) {
std::move(callback).Run(false);
}
void ContextualTasksExtensionHandler::RecordNextboxAnimationImpression(
bool shown) {}
void ContextualTasksExtensionHandler::OnContextMenuOpened() {}
// searchbox::mojom::PageHandler stubs:
void ContextualTasksExtensionHandler::OnFocusChanged(bool focused) {}
void ContextualTasksExtensionHandler::QueryAutocomplete(
int32_t query_id,
std::optional<int32_t> tab_id,
const std::u16string& input,
bool prevent_inline_autocomplete,
uint32_t cursor_position,
omnibox::SuggestInventory suggest_inventory,
bool is_on_focus,
const std::string& keyword,
searchbox::mojom::InputMethod input_method) {
DCHECK(!tab_id.has_value())
<< "QueryAutocomplete with tab_id is only supported for the full WebUI "
"Omnibox.";
}
void ContextualTasksExtensionHandler::StopAutocomplete(bool clear_result) {}
void ContextualTasksExtensionHandler::OpenAutocompleteMatch(
uint8_t line,
const GURL& url,
bool are_matches_showing,
uint8_t mouse_button,
searchbox::mojom::ActionModifiersPtr modifiers,
bool via_keyboard) {
NavigateUrl(url);
}
void ContextualTasksExtensionHandler::SetSmartComposeStats(
searchbox::mojom::SmartComposeStatsPtr smart_compose_stats) {}
void ContextualTasksExtensionHandler::SetPopupSelection(
searchbox::mojom::OmniboxPopupSelectionPtr selection) {}
void ContextualTasksExtensionHandler::OpenPopupSelection(
uint32_t result_sequence_id,
searchbox::mojom::OmniboxPopupSelectionPtr selection,
WindowOpenDisposition disposition) {}
void ContextualTasksExtensionHandler::OnNavigationLikely(
uint8_t line,
const GURL& url,
omnibox::mojom::NavigationPredictor navigation_predictor) {}
void ContextualTasksExtensionHandler::DeleteAutocompleteMatch(uint8_t line,
const GURL& url) {
}
void ContextualTasksExtensionHandler::ActivateKeyword(
uint8_t line,
const GURL& url,
base::TimeTicks match_selection_timestamp,
bool is_mouse_event) {}
void ContextualTasksExtensionHandler::ExecuteAction(
uint8_t line,
uint8_t action_index,
const GURL& url,
base::TimeTicks match_selection_timestamp,
uint8_t mouse_button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) {}
void ContextualTasksExtensionHandler::GetCyclingPlaceholderConfig(
GetCyclingPlaceholderConfigCallback callback) {
std::move(callback).Run(nullptr);
}
void ContextualTasksExtensionHandler::GetRecentTabs(
GetRecentTabsCallback callback) {
std::move(callback).Run({});
}
void ContextualTasksExtensionHandler::GetTabPreview(
int32_t tab_id,
GetTabPreviewCallback callback) {
std::move(callback).Run("");
}
void ContextualTasksExtensionHandler::WaitForTabFaviconLoad(
int32_t tab_id,
WaitForTabFaviconLoadCallback callback) {
std::move(callback).Run(std::nullopt);
}
void ContextualTasksExtensionHandler::GetInputState(
GetInputStateCallback callback) {
if (!input_state_model_) {
InitializeInputStateModel();
}
if (input_state_model_) {
std::move(callback).Run(input_state_model_->GetInputState());
} else {
std::move(callback).Run(std::nullopt);
}
}
void ContextualTasksExtensionHandler::NotifySessionStarted() {}
void ContextualTasksExtensionHandler::NotifySessionAbandoned() {}
void ContextualTasksExtensionHandler::AddFileContext(
searchbox::mojom::SelectedFileInfoPtr file_info,
mojo_base::BigBuffer file_bytes,
AddFileContextCallback callback) {}
void ContextualTasksExtensionHandler::AddTabContext(
int32_t tab_id,
bool delay_upload,
searchbox::mojom::TabAttachmentSource source,
AddTabContextCallback callback) {}
void ContextualTasksExtensionHandler::DeleteContext(
const base::UnguessableToken& file_token,
bool from_automatic_chip) {}
void ContextualTasksExtensionHandler::DeleteTabContext(int32_t tab_id) {}
void ContextualTasksExtensionHandler::ClearFiles(
bool should_block_auto_suggested_tabs) {}
void ContextualTasksExtensionHandler::SubmitQuery(const std::string& query_text,
uint8_t mouse_button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key,
bool is_voice_search) {
auto* session_handle = GetOrCreateContextualSessionHandle();
if (!session_handle) {
return;
}
std::optional<base::UnguessableToken> overlay_token = GetLensOverlayToken();
#if !BUILDFLAG(IS_ANDROID)
if (auto* controller = GetLensSearchController()) {
controller->CloseLensSync(
lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted);
}
#endif
auto request_info = contextual_tasks::PrepareClientToAimRequestInfo(
query_text, session_handle, this, active_tool_, active_model_,
GetActiveTabContextId(), overlay_token, is_voice_search);
contextual_tasks::FinalizeAndSendAimQuery(std::move(request_info),
session_handle, this);
}
void ContextualTasksExtensionHandler::OpenLensSearch() {}
void ContextualTasksExtensionHandler::SetActiveToolMode(omnibox::ToolMode tool,
bool is_set_by_server) {
active_tool_ = tool;
}
void ContextualTasksExtensionHandler::RecordToolSelectionAction(
omnibox::ToolMode tool) {}
void ContextualTasksExtensionHandler::SetActiveModelMode(
omnibox::ModelMode model,
bool is_set_by_aim) {
active_model_ = model;
}
void ContextualTasksExtensionHandler::RecordModelSelectionAction(
omnibox::ModelMode model) {}
void ContextualTasksExtensionHandler::ActivateMetricsFunnel(
const std::string& funnel_name) {}
void ContextualTasksExtensionHandler::GetDriveDisclaimerStatus(
GetDriveDisclaimerStatusCallback callback) {
std::move(callback).Run(
searchbox::mojom::DriveDisclaimerStatus::kNotAccepted);
}
void ContextualTasksExtensionHandler::OnDriveDisclaimerAccepted() {}
void ContextualTasksExtensionHandler::OnDriveUploadClicked(
OnDriveUploadClickedCallback callback) {}
void ContextualTasksExtensionHandler::OpenProfilePicker() {}
void ContextualTasksExtensionHandler::GetPageClassification(
GetPageClassificationCallback callback) {
std::move(callback).Run("INVALID_SPEC");
}
void ContextualTasksExtensionHandler::OnThumbnailRemoved() {}
void ContextualTasksExtensionHandler::PostAimMessage(
const lens::ClientToAimMessage& message) {
// Route the message directly to the extension page's bound remote.
if (contextual_tasks_page_.is_bound()) {
const size_t size = message.ByteSizeLong();
std::vector<uint8_t> serialized_message(size);
message.SerializeToArray(serialized_message.data(), size);
contextual_tasks_page_->PostAimMessage(std::move(serialized_message));
}
}
content::WebContents* ContextualTasksExtensionHandler::GetActiveTabWebContents()
const {
content::WebContents* host_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host());
if (!host_contents) {
return nullptr;
}
auto* browser_window_interface =
webui::GetBrowserWindowInterface(host_contents);
if (!browser_window_interface) {
return nullptr;
}
auto* active_tab = browser_window_interface->GetActiveTabInterface();
if (!active_tab) {
return nullptr;
}
return active_tab->GetContents();
}
contextual_search::ContextualSearchSessionHandle*
ContextualTasksExtensionHandler::GetOrCreateContextualSessionHandle() {
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host());
if (!web_contents) {
return nullptr;
}
auto* helper = ContextualSearchWebContentsHelper::GetOrCreateForWebContents(
web_contents);
contextual_search::ContextualSearchSessionHandle* existing_session =
task_id_.has_value() ? helper->GetSessionForTask(task_id_.value())
: helper->session_handle();
if (existing_session) {
return existing_session;
}
if (!task_id_) {
auto* browser_context = render_frame_host().GetBrowserContext();
Profile* profile = Profile::FromBrowserContext(browser_context);
auto* contextual_search_service =
ContextualSearchServiceFactory::GetForProfile(profile);
if (contextual_search_service) {
auto session_handle = contextual_search_service->CreateSession(
contextual_tasks::CreateQueryControllerConfigParams(),
contextual_search::ContextualSearchSource::kContextualTasks,
lens::LensOverlayInvocationSource::kContextualTasksComposebox);
session_handle->CheckSearchContentSharingSettings(profile->GetPrefs());
helper->SetTaskSession(std::nullopt, std::move(session_handle),
/*input_state_model=*/nullptr);
return helper->session_handle();
}
}
return existing_session;
}
std::optional<int64_t>
ContextualTasksExtensionHandler::GetActiveTabContextId() {
auto* contextual_session_handle = GetOrCreateContextualSessionHandle();
if (!contextual_session_handle) {
return std::nullopt;
}
content::WebContents* active_tab_contents = GetActiveTabWebContents();
if (!active_tab_contents) {
return std::nullopt;
}
SessionID active_tab_id =
sessions::SessionTabHelper::IdForTab(active_tab_contents);
if (!active_tab_id.is_valid()) {
return std::nullopt;
}
auto file_infos = contextual_session_handle->GetUploadedContextFileInfos();
auto submitted_file_infos =
contextual_session_handle->GetSubmittedContextFileInfos();
file_infos.insert(file_infos.end(), submitted_file_infos.begin(),
submitted_file_infos.end());
for (const auto& file_info : file_infos) {
if (file_info.tab_session_id &&
file_info.tab_session_id->id() == active_tab_id.id()) {
return file_info.GetContextId();
}
}
return std::nullopt;
}
std::optional<base::UnguessableToken>
ContextualTasksExtensionHandler::GetLensOverlayToken() {
#if !BUILDFLAG(IS_ANDROID)
if (auto* controller = GetLensSearchController()) {
auto* overlay = controller->lens_overlay_controller();
if (!overlay || !overlay->HasRegionSelection()) {
return std::nullopt;
}
if (auto* router = controller->query_router()) {
return router->overlay_tab_context_file_token();
}
}
#endif
return std::nullopt;
}
#if !BUILDFLAG(IS_ANDROID)
LensSearchController* ContextualTasksExtensionHandler::GetLensSearchController()
const {
content::WebContents* active_tab_contents = GetActiveTabWebContents();
if (!active_tab_contents) {
return nullptr;
}
return LensSearchController::FromTabWebContents(active_tab_contents);
}
#endif
void ContextualTasksExtensionHandler::InitializeInputStateModel() {
input_state_model_ = GetOrCreateInputStateModel();
if (!input_state_model_) {
return;
}
content::WebContents* active_tab_contents = GetActiveTabWebContents();
if (active_tab_contents) {
Profile* profile =
Profile::FromBrowserContext(active_tab_contents->GetBrowserContext());
if (profile) {
input_state_model_->SetPrefService(profile->GetPrefs());
}
}
input_state_subscription_ = input_state_model_->subscribe(
base::BindRepeating(&ContextualTasksExtensionHandler::OnInputStateChanged,
base::Unretained(this)));
input_state_model_->Initialize();
}
void ContextualTasksExtensionHandler::OnInputStateChanged(
const omnibox::InputState& state) {
if (searchbox_page_) {
searchbox_page_->OnInputStateChanged(state);
}
}
base::WeakPtr<contextual_search::InputStateModel>
ContextualTasksExtensionHandler::GetOrCreateInputStateModel() {
auto* session_handle = GetOrCreateContextualSessionHandle();
if (!session_handle) {
return nullptr;
}
content::WebContents* active_tab_contents = GetActiveTabWebContents();
if (!active_tab_contents) {
return nullptr;
}
auto* user_data =
contextual_tasks::ContextualTasksWebContentsUserData::FromWebContents(
active_tab_contents);
if (!user_data) {
contextual_tasks::ContextualTasksWebContentsUserData::CreateForWebContents(
active_tab_contents);
user_data =
contextual_tasks::ContextualTasksWebContentsUserData::FromWebContents(
active_tab_contents);
}
return user_data->GetOrCreateInputStateModel(*session_handle);
}
void ContextualTasksExtensionHandler::StartScreenshare(
bool prefer_entire_screen,
StartScreenshareCallback callback) {
std::move(callback).Run(std::nullopt);
}