blob: 380f3accb1e537b95d414d95d37d11f2b6f0efdc [file] [log] [blame]
// 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_search_controller.h"
#include "base/check.h"
#include "base/debug/dump_without_crashing.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/lens/core/mojom/geometry.mojom.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/lens/lens_composebox_controller.h"
#include "chrome/browser/ui/lens/lens_overlay_controller.h"
#include "chrome/browser/ui/lens/lens_overlay_event_handler.h"
#include "chrome/browser/ui/lens/lens_overlay_image_helper.h"
#include "chrome/browser/ui/lens/lens_overlay_query_controller.h"
#include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h"
#include "chrome/browser/ui/lens/lens_overlay_theme_utils.h"
#include "chrome/browser/ui/lens/lens_overlay_url_builder.h"
#include "chrome/browser/ui/lens/lens_permission_bubble_controller.h"
#include "chrome/browser/ui/lens/lens_search_contextualization_controller.h"
#include "chrome/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/browser/ui/lens/lens_searchbox_controller.h"
#include "chrome/browser/ui/lens/lens_session_metrics_logger.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/webui/webui_embedding_context.h"
#include "components/lens/lens_features.h"
#include "components/lens/lens_overlay_permission_utils.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/optimization_guide/content/browser/page_context_eligibility.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/rect.h"
namespace {
void CheckInitialized(bool initialized) {
CHECK(initialized)
<< "The LensSearchController has not been initialized. Initialize() must "
"be called before using the LensSearchController.";
}
} // namespace
DEFINE_USER_DATA(LensSearchController);
// static
LensSearchController* LensSearchController::From(tabs::TabInterface* tab) {
return tab ? Get(tab->GetUnownedUserDataHost()) : nullptr;
}
LensSearchController::LensSearchController(tabs::TabInterface* tab)
: tab_(tab),
scoped_unowned_user_data_(tab->GetUnownedUserDataHost(), *this) {
tab_subscriptions_.push_back(tab_->RegisterDidActivate(base::BindRepeating(
&LensSearchController::TabForegrounded, weak_ptr_factory_.GetWeakPtr())));
tab_subscriptions_.push_back(tab_->RegisterWillDeactivate(
base::BindRepeating(&LensSearchController::TabWillEnterBackground,
weak_ptr_factory_.GetWeakPtr())));
tab_subscriptions_.push_back(tab_->RegisterWillDiscardContents(
base::BindRepeating(&LensSearchController::WillDiscardContents,
weak_ptr_factory_.GetWeakPtr())));
tab_subscriptions_.push_back(tab_->RegisterWillDetach(base::BindRepeating(
&LensSearchController::WillDetach, weak_ptr_factory_.GetWeakPtr())));
}
LensSearchController::~LensSearchController() = default;
// TODO(crbug.com/404941800): Reconsider which of these controllers should be
// created in Initialize() vs created on demand when invoked.
void LensSearchController::Initialize(
variations::VariationsClient* variations_client,
signin::IdentityManager* identity_manager,
PrefService* pref_service,
syncer::SyncService* sync_service,
ThemeService* theme_service) {
CHECK(!initialized_);
initialized_ = true;
variations_client_ = variations_client;
identity_manager_ = identity_manager;
pref_service_ = pref_service;
sync_service_ = sync_service;
theme_service_ = theme_service;
// Create Gen204 controller first as query controller depends on it.
gen204_controller_ = std::make_unique<lens::LensOverlayGen204Controller>();
lens_overlay_controller_ = CreateLensOverlayController(
tab_, this, variations_client, identity_manager, pref_service,
sync_service, theme_service);
lens_overlay_side_panel_coordinator_ =
CreateLensOverlaySidePanelCoordinator();
lens_searchbox_controller_ = CreateLensSearchboxController();
lens_composebox_controller_ = CreateLensComposeboxController();
lens_contextualization_controller_ =
CreateLensSearchContextualizationController();
// Create the page context eligibility API as soon as possible as it is needed
// for every contextualization request.
lens_contextualization_controller_->CreatePageContextEligibilityAPI();
lens_overlay_event_handler_ =
std::make_unique<lens::LensOverlayEventHandler>(this);
lens_session_metrics_logger_ =
std::make_unique<lens::LensSessionMetricsLogger>();
}
// static.
LensSearchController* LensSearchController::FromWebUIWebContents(
content::WebContents* webui_web_contents) {
return From(webui::GetTabInterface(webui_web_contents));
}
// static.
LensSearchController* LensSearchController::FromTabWebContents(
content::WebContents* tab_web_contents) {
tabs::TabInterface* tab =
tabs::TabInterface::MaybeGetFromContents(tab_web_contents);
if (!tab) {
// TODO(crbug.com/444404134): Instead of calling MaybeGetFromContents(),
// callers should be ensuring that the web contents is a tab. Dump to try to
// identify when it is not.
base::debug::DumpWithoutCrashing();
}
return From(tab);
}
void LensSearchController::OpenLensOverlay(
lens::LensOverlayInvocationSource invocation_source) {
CheckInitialized(initialized_);
// If the eligibility checks fail, do not procced with opening any UI.
if (!IsOff() || !RunLensEligibilityChecks(
invocation_source,
/*permission_granted_callback=*/base::BindRepeating(
&LensSearchController::OpenLensOverlay,
weak_ptr_factory_.GetWeakPtr(), invocation_source))) {
return;
}
// If flag enabled, perform an empty contextual query instead of opening the
// overlay as normal. For internal debugging only.
if (lens::features::IsLensOverlayForceEmptyCsbQueryEnabled()) {
IssueTextSearchRequest(
lens::LensOverlayInvocationSource::kContentAreaContextMenuText,
/*query_text=*/"",
/*additional_query_parameters=*/{},
// TODO(crbug.com/432490312): Match type here is likely not ideal.
// Investigate removing match type from this function.
AutocompleteMatchType::Type::SEARCH_SUGGEST,
/*is_zero_prefix_suggestion=*/false,
/*suppress_contextualization=*/false);
return;
}
// Setup all state necessary for this Lens session.
StartLensSession(invocation_source);
lens_overlay_controller_->ShowUI(invocation_source,
lens_overlay_query_controller_.get());
}
void LensSearchController::OpenLensOverlayWithPendingRegionFromBounds(
lens::LensOverlayInvocationSource invocation_source,
const gfx::Rect& tab_bounds,
const gfx::Rect& view_bounds,
const gfx::Rect& region_bounds,
const SkBitmap& region_bitmap) {
OpenLensOverlayWithPendingRegion(
invocation_source,
lens::GetCenterRotatedBoxFromTabViewAndImageBounds(
tab_bounds, view_bounds, region_bounds),
region_bitmap);
}
void LensSearchController::OpenLensOverlayWithPendingRegion(
lens::LensOverlayInvocationSource invocation_source,
lens::mojom::CenterRotatedBoxPtr region,
const SkBitmap& region_bitmap) {
// If the eligibility checks fail, do not procced with opening any UI.
if (!IsOff() ||
!RunLensEligibilityChecks(
invocation_source,
/*permission_granted_callback=*/base::BindRepeating(
&LensSearchController::OpenLensOverlayWithPendingRegion,
weak_ptr_factory_.GetWeakPtr(), invocation_source,
base::Passed(region.Clone()), region_bitmap))) {
return;
}
// Setup all state necessary for this Lens session.
StartLensSession(invocation_source);
lens_overlay_controller_->ShowUIWithPendingRegion(
lens_overlay_query_controller_.get(), invocation_source,
std::move(region), region_bitmap);
}
void LensSearchController::StartContextualization(
lens::LensOverlayInvocationSource invocation_source) {
// If the eligibility checks fail, do not procced with opening any UI.
if (!IsOff() || !RunLensEligibilityChecks(
invocation_source,
/*permission_granted_callback=*/base::BindRepeating(
&LensSearchController::StartContextualization,
weak_ptr_factory_.GetWeakPtr(), invocation_source))) {
return;
}
// Setup all state necessary for this Lens session.
StartLensSession(invocation_source);
// TODO(crbug.com/418856988): Replace this with a call that starts
// contextualization without the unneeded callback.
lens_contextualization_controller_->StartContextualization(invocation_source,
base::DoNothing());
}
void LensSearchController::IssueContextualSearchRequest(
lens::LensOverlayInvocationSource invocation_source,
const GURL& destination_url,
AutocompleteMatchType::Type match_type,
bool is_zero_prefix_suggestion) {
// This method should only be used by the omnibox contextual suggestion flow.
// There is no dependency on the omnibox, so this check is solely to ensure a
// new flow is not accidentally added.
CHECK(invocation_source ==
lens::LensOverlayInvocationSource::kOmniboxContextualSuggestion);
std::string query_text =
lens::ExtractTextQueryParameterValue(destination_url);
std::map<std::string, std::string> additional_query_parameters =
lens::GetParametersMapWithoutQuery(destination_url);
IssueTextSearchRequest(
invocation_source, query_text, additional_query_parameters, match_type,
is_zero_prefix_suggestion, /*suppress_contextualization=*/false);
}
void LensSearchController::IssueTextSearchRequest(
lens::LensOverlayInvocationSource invocation_source,
std::string query_text,
std::map<std::string, std::string> additional_query_parameters,
AutocompleteMatchType::Type match_type,
bool is_zero_prefix_suggestion,
bool suppress_contextualization) {
// If the eligibility checks fail, do not procced with opening any UI.
if (!RunLensEligibilityChecks(
invocation_source,
/*permission_granted_callback=*/base::BindRepeating(
&LensSearchController::IssueTextSearchRequest,
weak_ptr_factory_.GetWeakPtr(), invocation_source, query_text,
additional_query_parameters, match_type,
is_zero_prefix_suggestion, suppress_contextualization))) {
return;
}
if (IsOff()) {
// If the state is off, the Lens sessions needs to be initialized.
StartLensSession(invocation_source, suppress_contextualization);
}
// TODO(crbug.com/404941800): This flow should not start the overlay once
// contextualization is separated from the overlay.
lens_overlay_controller_->IssueTextSearchRequest(
query_text, additional_query_parameters,
lens_overlay_query_controller_.get(), match_type,
is_zero_prefix_suggestion, invocation_source);
}
void LensSearchController::CloseLensAsync(
lens::LensOverlayDismissalSource dismissal_source) {
if (state() == State::kOff) {
return;
}
// Close the side panel if it is showing. This provides a smooth closing
// animation.
auto* side_panel_coordinator =
tab_->GetBrowserWindowInterface()->GetFeatures().side_panel_coordinator();
CHECK(side_panel_coordinator);
if (state_ == State::kActive && side_panel_coordinator->GetCurrentEntryId() ==
SidePanelEntry::Id::kLensOverlayResults) {
// If a close was triggered while the Lens side panel is showing, instead of
// just immediately closing all UI, the side panel should close to show a
// smooth closing animation. Once the side panel deregisters, it will
// recall the close method in OnSidePanelHidden() which will finish the
// closing process.
state_ = State::kClosingSidePanel;
last_dismissal_source_ = dismissal_source;
side_panel_coordinator->Close();
// Also trigger the overlay fade out animation, but don't pass a callback
// to finish the closing process since the side panel will call
// the finish closing process callback in OnSidePanelHidden().
lens_overlay_controller_->TriggerOverlayFadeOutAnimation(base::DoNothing());
return;
}
state_ = State::kClosing;
// If the overlay is showing, and the side panel is not, the overlay needs to
// fade out. Play the fade out animation and then clean up the rest of the UI
// afterwards.
if (lens_overlay_controller_->state() != LensOverlayController::State::kOff) {
lens_overlay_controller_->TriggerOverlayFadeOutAnimation(
base::BindOnce(&LensSearchController::CloseLensPart2,
weak_ptr_factory_.GetWeakPtr(), dismissal_source));
} else {
CloseLensPart2(dismissal_source);
}
}
void LensSearchController::CloseLensSync(
lens::LensOverlayDismissalSource dismissal_source) {
if (state() == State::kOff) {
return;
}
state_ = State::kClosing;
CloseLensPart2(dismissal_source);
}
void LensSearchController::HideOverlay(
lens::LensOverlayDismissalSource dismissal_source) {
if (state() == State::kOff) {
return;
}
// If the overlay is showing, the overlay needs to fade out. Play the fade out
// animation and then clean up the rest of the UI afterwards.
if (lens_overlay_controller_->state() != LensOverlayController::State::kOff) {
lens_overlay_controller_->TriggerOverlayFadeOutAnimation(
base::BindOnce(&LensSearchController::OnOverlayHidden,
weak_ptr_factory_.GetWeakPtr(), dismissal_source));
}
}
void LensSearchController::HideOverlay() {
if (state() == State::kOff) {
return;
}
// This method should only be called when the side panel is open.
DCHECK(lens_overlay_side_panel_coordinator_->state() !=
lens::LensOverlaySidePanelCoordinator::State::kOff);
// If the overlay is showing, the overlay needs to fade out. Play the fade out
// animation and then clean up the rest of the UI afterwards.
if (lens_overlay_controller_->state() != LensOverlayController::State::kOff) {
lens_overlay_controller_->TriggerOverlayFadeOutAnimation(
base::BindOnce(&LensSearchController::OnOverlayHidden,
weak_ptr_factory_.GetWeakPtr(), std::nullopt));
}
}
void LensSearchController::MaybeLaunchSurvey() {
if (!base::FeatureList::IsEnabled(lens::features::kLensOverlaySurvey)) {
return;
}
if (hats_triggered_in_session_) {
return;
}
HatsService* hats_service = HatsServiceFactory::GetForProfile(
tab_->GetBrowserWindowInterface()->GetProfile(),
/*create_if_necessary=*/true);
if (!hats_service) {
// HaTS may not be available in e.g. guest profile
return;
}
hats_triggered_in_session_ = true;
hats_service->LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerLensOverlayResults, tab_->GetContents(),
lens::features::GetLensOverlaySurveyResultsTime().InMilliseconds(),
/*product_specific_bits_data=*/{},
/*product_specific_string_data=*/
{{"ID that's tied to your Google Lens session",
base::NumberToString(lens_overlay_query_controller_->gen204_id())}});
}
bool LensSearchController::IsActive() {
return state_ == State::kActive;
}
bool LensSearchController::IsOff() {
return state_ == State::kOff;
}
bool LensSearchController::IsClosing() {
return state_ == State::kClosing || state_ == State::kClosingSidePanel;
}
bool LensSearchController::IsHandshakeComplete() {
const auto& suggest_inputs =
lens_searchbox_controller_->GetLensSuggestInputs();
return AreLensSuggestInputsReady(suggest_inputs);
}
tabs::TabInterface* LensSearchController::GetTabInterface() {
return tab_;
}
const GURL& LensSearchController::GetPageURL() const {
if (lens::CanSharePageURLWithLensOverlay(pref_service_)) {
return tab_->GetContents()->GetVisibleURL();
}
return GURL::EmptyGURL();
}
std::optional<std::string> LensSearchController::GetPageTitle() {
std::optional<std::string> page_title;
content::WebContents* active_web_contents = tab_->GetContents();
if (lens::CanSharePageTitleWithLensOverlay(sync_service_, pref_service_)) {
page_title = std::make_optional<std::string>(
base::UTF16ToUTF8(active_web_contents->GetTitle()));
}
return page_title;
}
base::WeakPtr<LensSearchController> LensSearchController::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
LensOverlayController* LensSearchController::lens_overlay_controller() {
CheckInitialized(initialized_);
return lens_overlay_controller_.get();
}
const LensOverlayController* LensSearchController::lens_overlay_controller()
const {
CheckInitialized(initialized_);
return lens_overlay_controller_.get();
}
lens::LensOverlayQueryController*
LensSearchController::lens_overlay_query_controller() {
CheckInitialized(initialized_);
return lens_overlay_query_controller_.get();
}
lens::LensOverlaySidePanelCoordinator*
LensSearchController::lens_overlay_side_panel_coordinator() {
CheckInitialized(initialized_);
return lens_overlay_side_panel_coordinator_.get();
}
lens::LensSearchboxController*
LensSearchController::lens_searchbox_controller() {
CheckInitialized(initialized_);
return lens_searchbox_controller_.get();
}
lens::LensComposeboxController*
LensSearchController::lens_composebox_controller() {
CheckInitialized(initialized_);
return lens_composebox_controller_.get();
}
lens::LensOverlayEventHandler*
LensSearchController::lens_overlay_event_handler() {
CheckInitialized(initialized_);
return lens_overlay_event_handler_.get();
}
lens::LensSearchContextualizationController*
LensSearchController::lens_search_contextualization_controller() {
CheckInitialized(initialized_);
return lens_contextualization_controller_.get();
}
lens::LensSessionMetricsLogger*
LensSearchController::lens_session_metrics_logger() {
CheckInitialized(initialized_);
return lens_session_metrics_logger_.get();
}
std::unique_ptr<LensOverlayController>
LensSearchController::CreateLensOverlayController(
tabs::TabInterface* tab,
LensSearchController* lens_search_controller,
variations::VariationsClient* variations_client,
signin::IdentityManager* identity_manager,
PrefService* pref_service,
syncer::SyncService* sync_service,
ThemeService* theme_service) {
return std::make_unique<LensOverlayController>(
tab, lens_search_controller, variations_client, identity_manager,
pref_service, sync_service, theme_service);
}
std::unique_ptr<lens::LensOverlayQueryController>
LensSearchController::CreateLensQueryController(
lens::LensOverlayFullImageResponseCallback full_image_callback,
lens::LensOverlayUrlResponseCallback url_callback,
lens::LensOverlayInteractionResponseCallback interaction_callback,
lens::LensOverlaySuggestInputsCallback suggest_inputs_callback,
lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
lens::UploadProgressCallback upload_progress_callback,
variations::VariationsClient* variations_client,
signin::IdentityManager* identity_manager,
Profile* profile,
lens::LensOverlayInvocationSource invocation_source,
bool use_dark_mode,
lens::LensOverlayGen204Controller* gen204_controller) {
return std::make_unique<lens::LensOverlayQueryController>(
std::move(full_image_callback), std::move(url_callback),
std::move(interaction_callback), std::move(suggest_inputs_callback),
std::move(thumbnail_created_callback),
std::move(upload_progress_callback), variations_client, identity_manager,
profile, invocation_source, use_dark_mode, gen204_controller);
}
std::unique_ptr<lens::LensOverlaySidePanelCoordinator>
LensSearchController::CreateLensOverlaySidePanelCoordinator() {
return std::make_unique<lens::LensOverlaySidePanelCoordinator>(this);
}
std::unique_ptr<lens::LensSearchboxController>
LensSearchController::CreateLensSearchboxController() {
return std::make_unique<lens::LensSearchboxController>(this);
}
std::unique_ptr<lens::LensComposeboxController>
LensSearchController::CreateLensComposeboxController() {
Profile* profile =
Profile::FromBrowserContext(tab_->GetContents()->GetBrowserContext());
return std::make_unique<lens::LensComposeboxController>(this, profile);
}
std::unique_ptr<lens::LensSearchContextualizationController>
LensSearchController::CreateLensSearchContextualizationController() {
return std::make_unique<lens::LensSearchContextualizationController>(this);
}
std::unique_ptr<lens::LensOverlayQueryController>
LensSearchController::CreateLensQueryController(
lens::LensOverlayInvocationSource invocation_source) {
Profile* profile =
Profile::FromBrowserContext(tab_->GetContents()->GetBrowserContext());
return CreateLensQueryController(
base::BindRepeating(&LensSearchController::HandleStartQueryResponse,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&LensSearchController::HandleInteractionURLResponse,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&LensSearchController::HandleInteractionResponse,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&LensSearchController::HandleSuggestInputsResponse,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(&LensSearchController::HandleThumbnailCreated,
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(
&LensSearchController::HandlePageContentUploadProgress,
weak_ptr_factory_.GetWeakPtr()),
variations_client_, identity_manager_, profile,
/*invocation_source=*/invocation_source,
/*use_dark_mode=*/lens::LensOverlayShouldUseDarkMode(theme_service_),
gen204_controller_.get());
}
void LensSearchController::StartLensSession(
lens::LensOverlayInvocationSource invocation_source,
bool suppress_contextualization) {
state_ = State::kInitializing;
// Create the query controller to be used for the current invocation.
CHECK(!lens_overlay_query_controller_);
lens_overlay_query_controller_ = CreateLensQueryController(invocation_source);
// Start the current metrics logger session.
lens_session_metrics_logger_->OnSessionStart(invocation_source,
tab_->GetContents());
// Let the searchbox controller know that a new session has started so it can
// initialize any data needed for the searchbox.
lens_searchbox_controller_->OnSessionStart(suppress_contextualization);
// Reset session state.
hats_triggered_in_session_ = false;
}
bool LensSearchController::RunLensEligibilityChecks(
lens::LensOverlayInvocationSource invocation_source,
base::RepeatingClosure permission_granted_callback) {
// The UI should only show if the tab is in the foreground or if the tab web
// contents is not in a crash state.
if (!tab_->IsActivated() || tab_->GetContents()->IsCrashed()) {
return false;
}
// If the user hasn't granted permission, request user permission before
// showing the UI.
if (!lens::CanSharePageScreenshotWithLensOverlay(pref_service_) ||
(lens::IsLensOverlayContextualSearchboxEnabled() &&
!lens::CanSharePageContentWithLensOverlay(pref_service_))) {
if (!lens_permission_bubble_controller_) {
lens_permission_bubble_controller_ =
std::make_unique<lens::LensPermissionBubbleController>(
*tab_, pref_service_, invocation_source);
}
lens_permission_bubble_controller_->RequestPermission(
tab_->GetContents(), std::move(permission_granted_callback));
return false;
}
return true;
}
void LensSearchController::NotifyOverlayOpened() {
CHECK(state() != State::kOff);
// The search controller could be backgrounded as the overlay controller
// is being initialized. If so, set the backgrounded state to active and
// record the invocation instead of setting the current state to active.
if (state_ == State::kBackground) {
backgrounded_state_ = State::kActive;
} else {
state_ = State::kActive;
}
// Record the UMA for lens overlay invocation.
lens_session_metrics_logger_->RecordInvocation();
}
void LensSearchController::CloseLensPart2(
lens::LensOverlayDismissalSource dismissal_source) {
// Let the controllers know to cleanup.
// TODO(crbug.com/404941800): Move logging to a shared location to not be
// dependent on the overlay controller.
lens_overlay_controller_->CloseUI(dismissal_source);
lens_searchbox_controller_->CloseUI();
lens_composebox_controller_->CloseUI();
lens_permission_bubble_controller_.reset();
lens_contextualization_controller_->ResetState();
lens_overlay_side_panel_coordinator_->DeregisterEntryAndCleanup();
// Cleanup the query controller after the overlay controller to prevent
// dangling ptrs.
lens_overlay_query_controller_.reset();
// Record end of session metrics.
lens_session_metrics_logger_->RecordEndOfSessionMetrics(dismissal_source);
state_ = State::kOff;
}
void LensSearchController::OnOverlayHidden(
std::optional<lens::LensOverlayDismissalSource> dismissal_source) {
// If the side panel is not open, end the session.
if (lens_overlay_side_panel_coordinator_->state() ==
lens::LensOverlaySidePanelCoordinator::State::kOff) {
// The caller should not have called this function without a dismissal
// source if the side panel is not open, because it would leave the Lens
// session in an inconsistent state.
// TODO(crbug.com/440608864): Make this a CHECK once this is verified.
if (!dismissal_source.has_value()) {
// Exit early to avoid a crash.
DCHECK(dismissal_source.has_value());
return;
}
CloseLensPart2(dismissal_source.value());
return;
}
// Since the side panel is open and the overlay has smoothly faded out, hide
// the overlay to restore state to the live page.
lens_overlay_controller_->HideOverlayAndMaybeSetLivePageState();
}
void LensSearchController::OnSidePanelWillHide(
SidePanelEntryHideReason reason) {
// If the tab is not in the foreground, this is not relevant.
if (!tab_->IsActivated()) {
return;
}
if (!IsClosing()) {
if (reason == SidePanelEntryHideReason::kReplaced) {
// If the Lens side panel is being replaced, don't close the side panel.
// Instead, set the state and dismissal source and wait for
// OnSidePanelHidden to be called.
state_ = State::kClosingSidePanel;
last_dismissal_source_ =
lens::LensOverlayDismissalSource::kSidePanelEntryReplaced;
} else {
// Trigger the close animation and notify the overlay that the side
// panel is closing so that it can fade out the UI.
CloseLensAsync(lens::LensOverlayDismissalSource::kSidePanelCloseButton);
}
}
}
void LensSearchController::OnSidePanelHidden() {
if (state_ != State::kClosingSidePanel) {
return;
}
CHECK(last_dismissal_source_.has_value());
CloseLensPart2(*last_dismissal_source_);
last_dismissal_source_.reset();
}
void LensSearchController::HandleStartQueryResponse(
std::vector<lens::mojom::OverlayObjectPtr> objects,
lens::mojom::TextPtr text,
bool is_error) {
lens_contextualization_controller_->SetText(text.Clone());
lens_overlay_controller_->HandleStartQueryResponse(std::move(objects),
std::move(text), is_error);
}
void LensSearchController::HandleInteractionURLResponse(
lens::proto::LensOverlayUrlResponse response) {
lens_overlay_controller_->HandleInteractionURLResponse(response);
}
void LensSearchController::HandleInteractionResponse(
lens::mojom::TextPtr text) {
lens_overlay_controller_->HandleInteractionResponse(std::move(text));
}
void LensSearchController::HandleSuggestInputsResponse(
lens::proto::LensOverlaySuggestInputs suggest_inputs) {
lens_searchbox_controller_->HandleSuggestInputsResponse(suggest_inputs);
}
void LensSearchController::HandlePageContentUploadProgress(uint64_t position,
uint64_t total) {
lens_overlay_controller_->HandlePageContentUploadProgress(position, total);
}
void LensSearchController::HandleThumbnailCreated(
const std::string& thumbnail_bytes,
const SkBitmap& region_bitmap) {
lens_overlay_controller_->HandleRegionBitmapCreated(region_bitmap);
lens_searchbox_controller_->HandleThumbnailCreated(thumbnail_bytes);
}
void LensSearchController::TabForegrounded(tabs::TabInterface* tab) {
// Ignore the event if the search controller is not backgrounded.
if (state_ != State::kBackground) {
return;
}
// Notify the overlay controller of the tab foregrounded event so it can
// restore to the previous state.
lens_overlay_controller_->TabForegrounded(tab);
state_ = backgrounded_state_;
}
void LensSearchController::TabWillEnterBackground(tabs::TabInterface* tab) {
if (state_ == State::kOff) {
return;
}
// Ignore the event if the overlay is already backgrounded.
if (state_ == State::kBackground) {
return;
}
// If the overlay is not active when the tab is backgrounded, then the entire
// Lens session should be closed. Note that the overlay is considered active
// when it is hidden and the side panel is open.
if (!lens_overlay_controller_->IsOverlayActive()) {
CloseLensSync(
lens::LensOverlayDismissalSource::kTabBackgroundedWhileScreenshotting);
return;
}
// Notify the overlay controller of the tab will enter background event so
// it can hide the overlay.
lens_overlay_controller_->TabWillEnterBackground(tab);
backgrounded_state_ = state_;
state_ = State::kBackground;
}
void LensSearchController::WillDiscardContents(
tabs::TabInterface* tab,
content::WebContents* old_contents,
content::WebContents* new_contents) {
// Background tab contents discarded.
CloseLensSync(lens::LensOverlayDismissalSource::kTabContentsDiscarded);
}
void LensSearchController::WillDetach(tabs::TabInterface* tab,
tabs::TabInterface::DetachReason reason) {
// When dragging a tab into a new window, all window-specific state must be
// reset. As this flow is not fully functional, close the overlay regardless
// of `reason`. https://crbug.com/342921671.
switch (reason) {
case tabs::TabInterface::DetachReason::kDelete:
CloseLensSync(lens::LensOverlayDismissalSource::kTabClosed);
return;
case tabs::TabInterface::DetachReason::kInsertIntoOtherWindow:
CloseLensSync(lens::LensOverlayDismissalSource::kTabDragNewWindow);
return;
}
}