| // 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/omnibox/ai_mode_page_action_controller.h" |
| |
| #include "base/check.h" |
| #include "chrome/browser/autocomplete/aim_eligibility_service_factory.h" |
| #include "chrome/browser/ui/actions/chrome_action_id.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "chrome/browser/ui/omnibox/omnibox_controller.h" |
| #include "chrome/browser/ui/omnibox/omnibox_edit_model.h" |
| #include "chrome/browser/ui/omnibox/omnibox_view.h" |
| #include "chrome/browser/ui/page_action/page_action_icon_type.h" |
| #include "chrome/browser/ui/search/omnibox_utils.h" |
| #include "chrome/browser/ui/tabs/public/tab_features.h" |
| #include "chrome/browser/ui/views/location_bar/location_bar_view.h" |
| #include "chrome/browser/ui/views/page_action/page_action_controller.h" |
| #include "components/omnibox/browser/omnibox_triggered_feature_service.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "third_party/metrics_proto/omnibox_event.pb.h" |
| #include "ui/views/focus/focus_manager.h" |
| #include "ui/views/view.h" |
| |
| namespace { |
| |
| page_actions::PageActionController* GetPageActionController( |
| BrowserWindowInterface& bwi) { |
| tabs::TabInterface* tab_interface = bwi.GetActiveTabInterface(); |
| if (!tab_interface || !tab_interface->GetTabFeatures()) { |
| return nullptr; |
| } |
| |
| return tab_interface->GetTabFeatures()->page_action_controller(); |
| } |
| |
| void SetPageActionVisibility( |
| page_actions::PageActionController& page_action_controller, |
| bool visible) { |
| if (visible) { |
| page_action_controller.Show(kActionAiMode); |
| page_action_controller.ShowSuggestionChip(kActionAiMode, |
| {.should_animate = false}); |
| return; |
| } |
| |
| page_action_controller.HideSuggestionChip(kActionAiMode); |
| page_action_controller.Hide(kActionAiMode); |
| } |
| |
| } // namespace |
| |
| namespace omnibox { |
| |
| DEFINE_USER_DATA(AiModePageActionController); |
| |
| AiModePageActionController* AiModePageActionController::From( |
| BrowserWindowInterface* bwi) { |
| return bwi ? Get(bwi->GetUnownedUserDataHost()) : nullptr; |
| } |
| |
| void AiModePageActionController::OpenAiMode(OmniboxView& omnibox_view, |
| bool via_keyboard) { |
| omnibox_view.model()->OpenAiMode(via_keyboard); |
| } |
| |
| void AiModePageActionController::NotifyOmniboxTriggeredFeatureService( |
| const OmniboxView& omnibox_view) { |
| const auto* client = omnibox_view.controller() |
| ->autocomplete_controller() |
| ->autocomplete_provider_client(); |
| auto* triggered_feature_service = client->GetOmniboxTriggeredFeatureService(); |
| triggered_feature_service->FeatureTriggered( |
| ::metrics::OmniboxEventProto_Feature:: |
| OmniboxEventProto_Feature_AIM_PAGE_ACTION_OMNIBOX_ENTRYPOINT); |
| } |
| |
| bool AiModePageActionController::ShouldShowPageAction( |
| Profile* profile, |
| const views::View& location_bar_view, |
| const OmniboxView& omnibox_view) { |
| const auto* aim_eligibility_service = |
| AimEligibilityServiceFactory::GetForProfile(profile); |
| if (!OmniboxFieldTrial::IsAimOmniboxEntrypointEnabled( |
| aim_eligibility_service)) { |
| return false; |
| } |
| |
| // If the user is currently in keyword mode, then suppress the AIM entrypoint. |
| if (omnibox_view.model()->is_keyword_selected()) { |
| return false; |
| } |
| |
| // If the feature is enabled to hide the AIM entrypoint on user input, don't |
| // show the AIM entrypoint if the user typed text is non-empty. |
| if (base::FeatureList::IsEnabled(omnibox::kHideAimEntrypointOnUserInput) && |
| !omnibox_view.model()->user_text().empty()) { |
| return false; |
| } |
| |
| // Otherwise, we should show the AIM view if the focus is within any view in |
| // the location bar, including the omnibox, this view or any other page action |
| // icon views. |
| const views::FocusManager* const focus_manager = |
| location_bar_view.GetFocusManager(); |
| const bool has_focus = focus_manager && location_bar_view.Contains( |
| focus_manager->GetFocusedView()); |
| |
| // TODO(crbug.com/448234135): Remove this logic from the migrated path when |
| // Page Action framework supports suggestion chip queueing. |
| // |
| // Handle the edge case in non-NTP page context with omnibox focus and closed |
| // popup. In this case, we suppress the AIM page action in order to ensure |
| // that it doesn't get visually "sandwiched" in between the other page actions |
| // that show up in this state. |
| const auto page_classification = |
| omnibox_view.model()->GetPageClassification(); |
| const bool is_ntp = |
| (page_classification == ::metrics::OmniboxEventProto:: |
| INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS); |
| if (has_focus && !omnibox_view.model()->user_input_in_progress() && |
| !omnibox_view.model()->PopupIsOpen() && !is_ntp) { |
| return false; |
| } |
| |
| return has_focus; |
| } |
| |
| AiModePageActionController::AiModePageActionController( |
| BrowserWindowInterface& bwi, |
| Profile& profile, |
| LocationBarView& location_bar_view) |
| : bwi_(bwi), |
| profile_(profile), |
| location_bar_view_(location_bar_view), |
| omnibox_view_(*location_bar_view.GetOmniboxView()), |
| scoped_data_(bwi.GetUnownedUserDataHost(), *this) { |
| CHECK(IsPageActionMigrated(PageActionIconType::kAiMode)); |
| } |
| |
| AiModePageActionController::~AiModePageActionController() = default; |
| |
| void AiModePageActionController::UpdatePageAction() { |
| page_actions::PageActionController* page_action_controller = |
| GetPageActionController(*bwi_); |
| if (!page_action_controller) { |
| return; |
| } |
| |
| const bool is_visible = ShouldShowPageAction( |
| base::to_address(profile_), *location_bar_view_, *omnibox_view_); |
| |
| if (is_visible) { |
| NotifyOmniboxTriggeredFeatureService(*omnibox_view_); |
| } |
| SetPageActionVisibility(*page_action_controller, is_visible); |
| } |
| |
| } // namespace omnibox |