| // 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/ui/lens/lens_searchbox_controller.h" |
| |
| #include "chrome/browser/ui/lens/lens_overlay_controller.h" |
| #include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h" |
| #include "chrome/browser/ui/lens/lens_search_controller.h" |
| #include "chrome/browser/ui/webui/util/image_util.h" |
| #include "components/lens/proto/server/lens_overlay_response.pb.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/sessions/core/session_id.h" |
| #include "net/base/url_util.h" |
| #include "third_party/metrics_proto/omnibox_event.pb.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| // The url query param key for the search query. |
| inline constexpr char kTextQueryParameterKey[] = "q"; |
| } // namespace |
| |
| namespace lens { |
| |
| LensSearchboxController::LensSearchboxController( |
| LensSearchController* lens_search_controller) |
| : lens_search_controller_(lens_search_controller) {} |
| LensSearchboxController::~LensSearchboxController() = default; |
| |
| void LensSearchboxController::SetSidePanelSearchboxHandler( |
| std::unique_ptr<LensSearchboxHandler> handler) { |
| side_panel_searchbox_handler_ = std::move(handler); |
| } |
| |
| void LensSearchboxController::SetContextualSearchboxHandler( |
| std::unique_ptr<LensSearchboxHandler> handler) { |
| overlay_searchbox_handler_ = std::move(handler); |
| } |
| |
| void LensSearchboxController::ResetOverlaySearchboxHandler() { |
| overlay_searchbox_handler_.reset(); |
| } |
| |
| void LensSearchboxController::ResetSidePanelSearchboxHandler() { |
| side_panel_searchbox_handler_.reset(); |
| } |
| |
| void LensSearchboxController::SetSearchboxInputText(const std::string& text) { |
| if (side_panel_searchbox_handler_ && |
| side_panel_searchbox_handler_->IsRemoteBound()) { |
| side_panel_searchbox_handler_->SetInputText(text); |
| } else { |
| // If the side panel was not bound at the time of request, we store the |
| // query as pending to send it to the searchbox on bind. |
| pending_text_query_ = text; |
| } |
| } |
| |
| void LensSearchboxController::SetSearchboxThumbnail( |
| const std::string& thumbnail_uri) { |
| if (side_panel_searchbox_handler_ && |
| side_panel_searchbox_handler_->IsRemoteBound()) { |
| side_panel_searchbox_handler_->SetThumbnail(thumbnail_uri); |
| selected_region_thumbnail_uri_ = thumbnail_uri; |
| } else { |
| // If the side panel was not bound at the time of request, we store the |
| // thumbnail as pending to send it to the searchbox on bind. |
| pending_thumbnail_uri_ = thumbnail_uri; |
| } |
| } |
| |
| void LensSearchboxController::HandleThumbnailCreated( |
| const std::string& thumbnail_bytes) { |
| selected_region_thumbnail_uri_ = |
| webui::MakeDataURIForImage(base::as_byte_span(thumbnail_bytes), "jpeg"); |
| SetSearchboxThumbnail(selected_region_thumbnail_uri_); |
| } |
| |
| void LensSearchboxController::CloseUI() { |
| overlay_searchbox_handler_.reset(); |
| side_panel_searchbox_handler_.reset(); |
| selected_region_thumbnail_uri_ = ""; |
| pending_text_query_ = std::nullopt; |
| pending_thumbnail_uri_ = std::nullopt; |
| } |
| |
| bool LensSearchboxController::IsContextualSearchbox() const { |
| // TODO(crbug.com/405441183): This logic will break the side panel searchbox |
| // if there is no overlay, so it should be moved to a shared location. |
| return GetPageClassification() == |
| metrics::OmniboxEventProto::CONTEXTUAL_SEARCHBOX; |
| } |
| |
| void LensSearchboxController::GetIsContextualSearchbox( |
| GetIsContextualSearchboxCallback callback) { |
| std::move(callback).Run(IsContextualSearchbox()); |
| } |
| |
| const GURL& LensSearchboxController::GetPageURL() const { |
| return lens_search_controller_->GetPageURL(); |
| } |
| |
| SessionID LensSearchboxController::GetTabId() const { |
| return sessions::SessionTabHelper::IdForTab(GetTabWebContents()); |
| } |
| |
| metrics::OmniboxEventProto::PageClassification |
| LensSearchboxController::GetPageClassification() const { |
| // There are two cases where we are assuming to be in a contextual flow: |
| // 1) We are in the zero state with the overlay CSB showing. |
| // 2) A user has made a contextual query and the live page is now showing. |
| // TODO(crbug.com/404941800): Remove dependency on LensOverlayController. |
| // Instead, it should check if contextualization is currently active. |
| const LensOverlayController::State state = |
| lens_search_controller_->lens_overlay_controller()->state(); |
| if (state == LensOverlayController::State::kLivePageAndResults || |
| state == LensOverlayController::State::kOverlay) { |
| return metrics::OmniboxEventProto::CONTEXTUAL_SEARCHBOX; |
| } |
| return selected_region_thumbnail_uri_.empty() |
| ? metrics::OmniboxEventProto::SEARCH_SIDE_PANEL_SEARCHBOX |
| : metrics::OmniboxEventProto::LENS_SIDE_PANEL_SEARCHBOX; |
| } |
| |
| std::string& LensSearchboxController::GetThumbnail() { |
| return selected_region_thumbnail_uri_; |
| } |
| |
| const lens::proto::LensOverlaySuggestInputs& |
| LensSearchboxController::GetLensSuggestInputs() const { |
| // TODO(crbug.com/413138792): Implement suggest inputs tracking in this class. |
| return lens_search_controller_->lens_overlay_controller() |
| ->GetLensSuggestInputs(); |
| } |
| |
| void LensSearchboxController::OnTextModified() { |
| lens_search_controller_->lens_overlay_controller()->ClearTextSelection(); |
| } |
| |
| void LensSearchboxController::OnThumbnailRemoved() { |
| lens_search_controller_->lens_overlay_controller()->ClearRegionSelection(); |
| } |
| |
| void LensSearchboxController::OnSuggestionAccepted( |
| const GURL& destination_url, |
| AutocompleteMatchType::Type match_type, |
| bool is_zero_prefix_suggestion) { |
| std::string query_text = ""; |
| std::map<std::string, std::string> additional_query_parameters; |
| |
| net::QueryIterator query_iterator(destination_url); |
| while (!query_iterator.IsAtEnd()) { |
| std::string_view key = query_iterator.GetKey(); |
| std::string_view value = query_iterator.GetUnescapedValue(); |
| if (kTextQueryParameterKey == key) { |
| query_text = value; |
| } else { |
| additional_query_parameters.insert(std::make_pair( |
| query_iterator.GetKey(), query_iterator.GetUnescapedValue())); |
| } |
| query_iterator.Advance(); |
| } |
| |
| // TODO(crbug.com/413138792): Move the logic to issue a searchbox query to |
| // this class. |
| lens_search_controller_->lens_overlay_controller()->IssueSearchBoxRequest( |
| query_text, match_type, is_zero_prefix_suggestion, |
| additional_query_parameters); |
| } |
| |
| void LensSearchboxController::OnFocusChanged(bool focused) { |
| // TOOD(crbug.com/404941800): Implement OnSearchboxFocusChanged logic in this |
| // class. |
| lens_search_controller_->lens_overlay_controller()->OnSearchboxFocusChanged( |
| focused); |
| } |
| |
| void LensSearchboxController::OnPageBound() { |
| // If the side panel closes before the remote gets bound, |
| // side_panel_searchbox_handler_ could become unset. Verify it is set before |
| // sending to the side panel. |
| if (!side_panel_searchbox_handler_ || |
| !side_panel_searchbox_handler_->IsRemoteBound()) { |
| return; |
| } |
| |
| // Send any pending inputs for the searchbox. |
| if (pending_text_query_.has_value()) { |
| side_panel_searchbox_handler_->SetInputText(*pending_text_query_); |
| pending_text_query_.reset(); |
| } |
| if (pending_thumbnail_uri_.has_value()) { |
| SetSearchboxThumbnail(*pending_thumbnail_uri_); |
| pending_thumbnail_uri_.reset(); |
| } |
| } |
| |
| void LensSearchboxController::ShowGhostLoaderErrorState() { |
| // TODO(crbug.com/413138792): Move ghost loader handling logic to this class. |
| lens_search_controller_->lens_overlay_controller() |
| ->ShowGhostLoaderErrorState(); |
| } |
| |
| void LensSearchboxController::OnZeroSuggestShown() { |
| // TODO(crbug.com/413138792): Move the OnZeroSuggestShown() logic to this |
| // class. |
| lens_search_controller_->lens_overlay_controller()->OnZeroSuggestShown(); |
| } |
| |
| content::WebContents* LensSearchboxController::GetTabWebContents() const { |
| return lens_search_controller_->GetTabInterface()->GetContents(); |
| } |
| |
| void LensSearchboxController::AddSearchboxStateToSearchQuery( |
| lens::SearchQuery& search_query) { |
| search_query.selected_region_thumbnail_uri_ = selected_region_thumbnail_uri_; |
| } |
| |
| } // namespace lens |