blob: e1aa5622a0f402a8796765b2e49c7b2ecce4498c [file] [log] [blame]
// Copyright 2024 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/views/tabs/tab_strip_action_container.h"
#include <memory>
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/types/pass_key.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/actor/ui/actor_ui_metrics.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/organization/tab_declutter_controller.h"
#include "chrome/browser/ui/tabs/organization/tab_organization_service.h"
#include "chrome/browser/ui/tabs/organization/tab_organization_service_factory.h"
#include "chrome/browser/ui/tabs/organization/tab_organization_utils.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/browser/ui/views/commerce/product_specifications_button.h"
#include "chrome/browser/ui/views/tabs/glic_button.h"
#include "chrome/browser/ui/views/tabs/tab_strip_nudge_button.h"
#include "chrome/browser/ui/webui/tab_search/tab_search.mojom.h"
#include "chrome/common/chrome_features.h"
#include "chrome/grit/generated_resources.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/animation/tween.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/mouse_watcher.h"
#include "ui/views/mouse_watcher_view_host.h"
#include "ui/views/view_class_properties.h"
#if BUILDFLAG(ENABLE_GLIC)
#include "chrome/browser/glic/glic_profile_manager.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/browser/glic/host/host.h"
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/glic/public/glic_instance.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/glic/resources/grit/glic_browser_resources.h"
#include "chrome/browser/ui/tabs/glic_actor_task_icon_manager_factory.h"
#include "chrome/browser/ui/tabs/tab_style.h"
#include "chrome/grit/branded_strings.h"
#endif // BUILDFLAG(ENABLE_GLIC)
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class TriggerOutcome {
kAccepted = 0,
kDismissed = 1,
kTimedOut = 2,
kMaxValue = kTimedOut,
};
constexpr base::TimeDelta kExpansionInDuration = base::Milliseconds(500);
constexpr base::TimeDelta kExpansionOutDuration = base::Milliseconds(250);
constexpr base::TimeDelta kOpacityInDuration = base::Milliseconds(300);
constexpr base::TimeDelta kOpacityOutDuration = base::Milliseconds(100);
constexpr base::TimeDelta kOpacityDelay = base::Milliseconds(100);
constexpr char kAutoTabGroupsTriggerOutcomeName[] =
"Tab.Organization.Trigger.Outcome";
constexpr char kDeclutterTriggerOutcomeName[] =
"Tab.Organization.Declutter.Trigger.Outcome";
constexpr char kDeclutterTriggerBucketedCTRName[] =
"Tab.Organization.Declutter.Trigger.BucketedCTR";
#if BUILDFLAG(ENABLE_GLIC)
constexpr int kLargeSpaceBetweenButtons = 6;
constexpr int kInsideBorderAroundGlicButtons = 2;
constexpr int kOutsideBorderAroundGlicButtons = 11;
#if !BUILDFLAG(IS_MAC)
constexpr int kLargeSpaceBetweenSeparatorRight = 8;
constexpr int kLargeSpaceBetweenSeparatorLeft = 2;
#endif // !BUILDFLAG(IS_MAC)
#endif // BUILDFLAG(ENABLE_GLIC)
} // namespace
TabStripActionContainer::TabStripNudgeAnimationSession::
TabStripNudgeAnimationSession(TabStripNudgeButton* button,
TabStripActionContainer* container,
AnimationSessionType session_type,
base::OnceCallback<void()> on_animation_ended,
bool is_opacity_animated)
: button_(button),
container_(container),
expansion_animation_(container),
opacity_animation_(container),
session_type_(session_type),
on_animation_ended_(std::move(on_animation_ended)),
is_opacity_animated_(is_opacity_animated),
is_executing_show_or_hide_(false) {
if (session_type_ == AnimationSessionType::HIDE) {
expansion_animation_.Reset(1);
if (is_opacity_animated) {
opacity_animation_.Reset(1);
}
}
}
TabStripActionContainer::TabStripNudgeAnimationSession::
~TabStripNudgeAnimationSession() = default;
void TabStripActionContainer::TabStripNudgeAnimationSession::Start() {
if (session_type_ ==
TabStripNudgeAnimationSession::AnimationSessionType::SHOW) {
Show();
} else {
Hide();
}
}
void TabStripActionContainer::TabStripNudgeAnimationSession::
ResetExpansionAnimationForTesting(double value) {
expansion_animation_.Reset(value);
}
void TabStripActionContainer::TabStripNudgeAnimationSession::
ResetOpacityAnimationForTesting(double value) {
if (is_opacity_animated_) {
if (opacity_animation_delay_timer_.IsRunning()) {
opacity_animation_delay_timer_.FireNow();
}
}
if (is_opacity_animated_) {
opacity_animation_.Reset(value);
}
}
void TabStripActionContainer::TabStripNudgeAnimationSession::Show() {
base::AutoReset<bool> resetter(&is_executing_show_or_hide_, true);
expansion_animation_.SetTweenType(gfx::Tween::Type::ACCEL_20_DECEL_100);
if (is_opacity_animated_) {
opacity_animation_.SetTweenType(gfx::Tween::Type::LINEAR);
}
expansion_animation_.SetSlideDuration(
GetAnimationDuration(kExpansionInDuration));
expansion_animation_.Show();
if (is_opacity_animated_) {
opacity_animation_.SetSlideDuration(
GetAnimationDuration(kOpacityInDuration));
const base::TimeDelta delay = GetAnimationDuration(kOpacityDelay);
opacity_animation_delay_timer_.Start(
FROM_HERE, delay, this,
&TabStripActionContainer::TabStripNudgeAnimationSession::
ShowOpacityAnimation);
}
}
void TabStripActionContainer::TabStripNudgeAnimationSession::Hide() {
base::AutoReset<bool> resetter(&is_executing_show_or_hide_, true);
// Animate and hide existing chip.
if (session_type_ ==
TabStripNudgeAnimationSession::AnimationSessionType::SHOW) {
if (is_opacity_animated_ && opacity_animation_delay_timer_.IsRunning()) {
opacity_animation_delay_timer_.FireNow();
}
session_type_ = TabStripNudgeAnimationSession::AnimationSessionType::HIDE;
}
expansion_animation_.SetTweenType(gfx::Tween::Type::ACCEL_20_DECEL_100);
expansion_animation_.SetSlideDuration(
GetAnimationDuration(kExpansionOutDuration));
expansion_animation_.Hide();
if (is_opacity_animated_) {
opacity_animation_.SetTweenType(gfx::Tween::Type::LINEAR);
opacity_animation_.SetSlideDuration(
GetAnimationDuration(kOpacityOutDuration));
opacity_animation_.Hide();
}
}
base::TimeDelta
TabStripActionContainer::TabStripNudgeAnimationSession::GetAnimationDuration(
base::TimeDelta duration) {
return gfx::Animation::ShouldRenderRichAnimation() ? duration
: base::TimeDelta();
}
void TabStripActionContainer::TabStripNudgeAnimationSession::
ShowOpacityAnimation() {
opacity_animation_.Show();
}
void TabStripActionContainer::TabStripNudgeAnimationSession::
ApplyAnimationValue(const gfx::Animation* animation) {
float value = animation->GetCurrentValue();
if (animation == &expansion_animation_) {
button_->SetWidthFactor(value);
} else if (animation == &opacity_animation_) {
button_->SetOpacity(value);
}
}
void TabStripActionContainer::TabStripNudgeAnimationSession::MarkAnimationDone(
const gfx::Animation* animation) {
if (animation == &expansion_animation_) {
expansion_animation_done_ = true;
} else {
opacity_animation_done_ = true;
}
const bool opacity_animation_not_running =
opacity_animation_done_ || !is_opacity_animated_;
if (expansion_animation_done_ && opacity_animation_not_running) {
if (on_animation_ended_) {
if (is_executing_show_or_hide_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_animation_ended_));
} else {
std::move(on_animation_ended_).Run();
}
}
}
}
TabStripActionContainer::TabStripActionContainer(
TabStripController* tab_strip_controller,
tabs::TabDeclutterController* tab_declutter_controller,
tabs::GlicNudgeController* glic_nudge_controller)
: AnimationDelegateViews(this),
locked_expansion_view_(this),
tab_declutter_controller_(tab_declutter_controller),
glic_nudge_controller_(glic_nudge_controller),
tab_strip_controller_(tab_strip_controller) {
SetProperty(views::kElementIdentifierKey, kTabStripActionContainerElementId);
mouse_watcher_ = std::make_unique<views::MouseWatcher>(
std::make_unique<views::MouseWatcherViewHost>(locked_expansion_view_,
gfx::Insets()),
this);
tab_organization_service_ = TabOrganizationServiceFactory::GetForProfile(
tab_strip_controller->GetProfile());
if (tab_organization_service_) {
tab_organization_observation_.Observe(tab_organization_service_);
}
// `tab_declutter_controller_` will be null for some profile types and if
// feature is not enabled.
if (tab_declutter_controller_) {
tab_declutter_button_ =
AddChildView(CreateTabDeclutterButton(tab_strip_controller));
SetupButtonProperties(tab_declutter_button_);
tab_declutter_observation_.Observe(tab_declutter_controller_);
}
// `glic_nudge_controller_` will be null if feature is not enabled.
if (glic_nudge_controller_) {
#if BUILDFLAG(ENABLE_GLIC)
glic_nudge_controller_->SetDelegate(this);
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
auto_tab_group_button_ =
AddChildView(CreateAutoTabGroupButton(tab_strip_controller));
SetupButtonProperties(auto_tab_group_button_);
browser_ = tab_strip_controller->GetBrowser();
BrowserWindowInterface* browser_window_interface =
tab_strip_controller->GetBrowserWindowInterface();
if (base::FeatureList::IsEnabled(commerce::kProductSpecifications)) {
std::unique_ptr<ProductSpecificationsButton> product_specifications_button;
product_specifications_button =
std::make_unique<ProductSpecificationsButton>(
tab_strip_controller, browser_window_interface->GetTabStripModel(),
commerce::ProductSpecificationsEntryPointController::From(
browser_window_interface),
/*render_tab_search_before_tab_strip_*/ false, this);
product_specifications_button->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
product_specifications_button_ =
AddChildView(std::move(product_specifications_button));
}
#if BUILDFLAG(ENABLE_GLIC)
if (glic::GlicEnabling::IsProfileEligible(
tab_strip_controller->GetProfile())) {
if (features::kGlicActorUiTaskIcon.Get()) {
glic_actor_button_container_ =
AddChildView(CreateGlicActorButtonContainer());
glic_actor_task_icon_ = glic_actor_button_container_->AddChildView(
CreateGlicActorTaskIcon(tab_strip_controller));
glic_actor_button_container_->SetVisible(false);
}
glic_button_ = AddChildView(CreateGlicButton(tab_strip_controller));
SetupButtonProperties(glic_button_);
#if !BUILDFLAG(IS_MAC)
std::unique_ptr<views::Separator> separator =
std::make_unique<views::Separator>();
separator->SetBorderRadius(TabStyle::Get()->GetSeparatorCornerRadius());
separator->SetPreferredSize(TabStyle::Get()->GetSeparatorSize());
separator->SetColorId(kColorTabDividerFrameActive);
gfx::Insets margin;
margin.set_left_right(kLargeSpaceBetweenSeparatorLeft,
kLargeSpaceBetweenSeparatorRight);
separator->SetProperty(views::kMarginsKey, margin);
subscriptions_.push_back(browser_window_interface->RegisterDidBecomeActive(
base::BindRepeating(&TabStripActionContainer::DidBecomeActive,
base::Unretained(this))));
subscriptions_.push_back(
browser_window_interface->RegisterDidBecomeInactive(
base::BindRepeating(&TabStripActionContainer::DidBecomeInactive,
base::Unretained(this))));
separator_ = AddChildView(std::move(separator));
#endif // !BUILDFLAG(IS_MAC)
}
#endif // BUILDFLAG(ENABLE_GLIC)
SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetMainAxisAlignment(views::LayoutAlignment::kStart)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
.SetCollapseMargins(false);
}
TabStripActionContainer::~TabStripActionContainer() {
if (scoped_tab_strip_modal_ui_) {
scoped_tab_strip_modal_ui_.reset();
}
if (glic_nudge_controller_) {
glic_nudge_controller_->SetDelegate(nullptr);
}
}
std::unique_ptr<TabStripNudgeButton>
TabStripActionContainer::CreateTabDeclutterButton(
TabStripController* tab_strip_controller) {
auto button = std::make_unique<TabStripNudgeButton>(
tab_strip_controller,
base::BindRepeating(&TabStripActionContainer::OnTabDeclutterButtonClicked,
base::Unretained(this)),
base::BindRepeating(
&TabStripActionContainer::OnTabDeclutterButtonDismissed,
base::Unretained(this)),
features::IsTabstripDedupeEnabled()
? l10n_util::GetStringUTF16(IDS_TAB_DECLUTTER)
: l10n_util::GetStringUTF16(IDS_TAB_DECLUTTER_NO_DEDUPE),
kTabDeclutterButtonElementId, Edge::kNone, gfx::VectorIcon::EmptyIcon(),
/*show_close_button=*/true);
button->SetTooltipText(
features::IsTabstripDedupeEnabled()
? l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_DECLUTTER)
: l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_DECLUTTER_NO_DEDUPE));
button->GetViewAccessibility().SetName(
features::IsTabstripDedupeEnabled()
? l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_DECLUTTER)
: l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_DECLUTTER_NO_DEDUPE));
button->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
return button;
}
std::unique_ptr<TabStripNudgeButton>
TabStripActionContainer::CreateAutoTabGroupButton(
TabStripController* tab_strip_controller) {
auto button = std::make_unique<TabStripNudgeButton>(
tab_strip_controller,
base::BindRepeating(&TabStripActionContainer::OnAutoTabGroupButtonClicked,
base::Unretained(this)),
base::BindRepeating(
&TabStripActionContainer::OnAutoTabGroupButtonDismissed,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_TAB_ORGANIZE), kAutoTabGroupButtonElementId,
Edge::kNone, gfx::VectorIcon::EmptyIcon(), /*show_close_button=*/true);
button->SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_ORGANIZE));
button->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_ORGANIZE));
button->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
return button;
}
#if BUILDFLAG(ENABLE_GLIC)
std::unique_ptr<glic::GlicButton> TabStripActionContainer::CreateGlicButton(
TabStripController* tab_strip_controller) {
glic::GlicKeyedService* service =
glic::GlicKeyedService::Get(tab_strip_controller_->GetProfile());
std::u16string tooltip_text = l10n_util::GetStringUTF16(
service->IsWindowOrFreShowing() ? IDS_GLIC_TAB_STRIP_BUTTON_TOOLTIP_CLOSE
: IDS_GLIC_TAB_STRIP_BUTTON_TOOLTIP);
std::unique_ptr<glic::GlicButton> glic_button =
std::make_unique<glic::GlicButton>(
tab_strip_controller,
base::BindRepeating(&TabStripActionContainer::OnGlicButtonClicked,
base::Unretained(this)),
base::BindRepeating(&TabStripActionContainer::OnGlicButtonDismissed,
base::Unretained(this)),
base::BindRepeating(&TabStripActionContainer::OnGlicButtonHovered,
base::Unretained(this)),
base::BindRepeating(&TabStripActionContainer::OnGlicButtonMouseDown,
base::Unretained(this)),
base::BindRepeating(
&TabStripActionContainer::OnGlicButtonAnimationEnded,
base::Unretained(this)),
tooltip_text);
glic_button->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
return glic_button;
}
std::unique_ptr<glic::GlicActorTaskIcon>
TabStripActionContainer::CreateGlicActorTaskIcon(
TabStripController* tab_strip_controller) {
std::unique_ptr<glic::GlicActorTaskIcon> glic_actor_task_icon =
std::make_unique<glic::GlicActorTaskIcon>(
tab_strip_controller,
base::BindRepeating(
&TabStripActionContainer::OnGlicActorTaskIconClicked,
base::Unretained(this)));
glic_actor_task_icon->SetProperty(views::kCrossAxisAlignmentKey,
views::LayoutAlignment::kCenter);
return glic_actor_task_icon;
}
// TODO(crbug.com/431015299): Clean up when GlicButton and GlicActorTaskIcon
// have been combined.
std::unique_ptr<views::FlexLayoutView>
TabStripActionContainer::CreateGlicActorButtonContainer() {
auto glic_actor_button_container = std::make_unique<views::FlexLayoutView>();
glic_actor_button_container->SetCollapseMargins(true);
glic_actor_button_container->SetCrossAxisAlignment(
views::LayoutAlignment::kCenter);
glic_actor_button_container->SetBackground(views::CreateRoundedRectBackground(
kColorNewTabButtonCRBackgroundFrameActive, gfx::RoundedCornersF(12),
gfx::Insets::VH(4, 8)));
// Should be hidden until a task starts.
glic_actor_button_container->SetVisible(false);
return glic_actor_button_container;
}
void TabStripActionContainer::UpdateGlicActorButtonContainerBorders() {
CHECK(glic_button_);
gfx::Insets glic_border;
// GlicActorTaskIcon will only ever be shown alongside the GlicButton.
if (glic_actor_task_icon_ && glic_actor_task_icon_->IsDrawn()) {
gfx::Insets task_icon_border;
const gfx::Insets right_icon_border = gfx::Insets().set_left_right(
kInsideBorderAroundGlicButtons, kOutsideBorderAroundGlicButtons);
const gfx::Insets left_icon_border = gfx::Insets().set_left_right(
kOutsideBorderAroundGlicButtons, kInsideBorderAroundGlicButtons);
if (features::kGlicActorUiNudgeRedesign.Get()) {
task_icon_border = right_icon_border + border_insets_;
// If the GlicActorTaskIcon is also present, adjust the border on the
// GlicButton to allow the two buttons to sit closer together.
glic_border = left_icon_border + border_insets_;
} else {
task_icon_border = left_icon_border + border_insets_;
glic_border = right_icon_border + border_insets_;
}
glic_actor_task_icon_->SetBorder(
views::CreateEmptyBorder(task_icon_border));
} else {
// Reset GlicButton border if Task Icon is hidden.
glic_border = gfx::Insets().set_left_right(border_insets_.top(),
border_insets_.bottom()) +
border_insets_;
}
glic_button_->SetBorder(views::CreateEmptyBorder(glic_border));
}
#endif // BUILDFLAG(ENABLE_GLIC)
void TabStripActionContainer::OnToggleActionUIState(const Browser* browser,
bool should_show) {
CHECK(tab_organization_service_);
if (locked_expansion_mode_ != LockedExpansionMode::kNone) {
return;
}
if (should_show && browser_ == browser) {
ShowTabStripNudge(auto_tab_group_button_);
} else {
HideTabStripNudge(auto_tab_group_button_);
}
}
void TabStripActionContainer::OnTabDeclutterButtonClicked() {
tabs::TabDeclutterController::EmitEntryPointHistogram(
tab_search::mojom::TabDeclutterEntryPoint::kNudge);
base::UmaHistogramEnumeration(kDeclutterTriggerOutcomeName,
TriggerOutcome::kAccepted);
LogDeclutterTriggerBucket(true);
browser_->window()->CreateTabSearchBubble(
tab_search::mojom::TabSearchSection::kOrganize,
tab_search::mojom::TabOrganizationFeature::kDeclutter);
ExecuteHideTabStripNudge(tab_declutter_button_);
}
void TabStripActionContainer::OnTabDeclutterButtonDismissed() {
base::UmaHistogramEnumeration(kDeclutterTriggerOutcomeName,
TriggerOutcome::kDismissed);
tab_declutter_controller_->OnActionUIDismissed(
base::PassKey<TabStripActionContainer>());
ExecuteHideTabStripNudge(tab_declutter_button_);
}
void TabStripActionContainer::OnTriggerDeclutterUIVisibility() {
CHECK(tab_declutter_controller_);
ShowTabStripNudge(tab_declutter_button_);
}
#if BUILDFLAG(ENABLE_GLIC)
void TabStripActionContainer::OnGlicButtonClicked() {
// Indicate that the glic button was pressed so that we can either close the
// IPH promo (if present) or note that it has already been used to prevent
// unnecessarily displaying the promo.
BrowserUserEducationInterface::From(
tab_strip_controller_->GetBrowserWindowInterface())
->NotifyFeaturePromoFeatureUsed(
feature_engagement::kIPHGlicPromoFeature,
FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
std::optional<std::string> prompt_suggestion;
if (glic_nudge_controller_) {
prompt_suggestion = glic_nudge_controller_->GetPromptSuggestion();
glic_nudge_controller_->ClearPromptSuggestion();
}
glic::GlicKeyedServiceFactory::GetGlicKeyedService(
tab_strip_controller_->GetProfile())
->ToggleUI(tab_strip_controller_->GetBrowserWindowInterface(),
/*prevent_close=*/false,
glic_button_->GetIsShowingNudge()
? glic::mojom::InvocationSource::kNudge
: glic::mojom::InvocationSource::kTopChromeButton,
prompt_suggestion);
if (glic_button_->GetIsShowingNudge()) {
glic_nudge_controller_->OnNudgeActivity(
tabs::GlicNudgeActivity::kNudgeClicked);
}
ExecuteHideTabStripNudge(glic_button_);
// Reset state manually since there wont be a mouse up event as the animation
// moves the button out of the way.
glic_button_->SetState(views::Button::ButtonState::STATE_NORMAL);
}
void TabStripActionContainer::OnGlicButtonDismissed() {
glic_nudge_controller_->OnNudgeActivity(
tabs::GlicNudgeActivity::kNudgeDismissed);
// Force hide the button when pressed, bypassing locked expansion mode.
ExecuteHideTabStripNudge(glic_button_);
}
void TabStripActionContainer::OnGlicButtonHovered() {
Profile* profile = tab_strip_controller_->GetProfile();
glic::GlicKeyedService* glic_service =
glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile);
if (auto* instance = glic_service->GetInstanceForActiveTab(
tab_strip_controller_->GetBrowserWindowInterface())) {
instance->host().instance_delegate().PrepareForOpen();
}
}
void TabStripActionContainer::OnGlicButtonMouseDown() {
Profile* profile = tab_strip_controller_->GetProfile();
if (!glic::GlicEnabling::IsEnabledAndConsentForProfile(profile)) {
// Do not do this optimization if user has not consented to GLIC.
return;
}
auto* glic_service = glic::GlicKeyedService::Get(profile);
// TODO(crbug.com/445934142): Create the instance here so that suggestions can
// be fetched, but don't show it yet.
if (auto* instance = glic_service->GetInstanceForActiveTab(
tab_strip_controller_->GetBrowserWindowInterface())) {
// This prefetches the results and allows the underlying implementation to
// cache the results for future calls. Which is why the callback does
// nothing.
instance->host().instance_delegate().FetchZeroStateSuggestions(
/*is_first_run=*/false, /*supported_tools=*/std::nullopt,
base::DoNothing());
}
}
void TabStripActionContainer::OnGlicButtonAnimationEnded() {
if (!glic_button_->GetIsShowingNudge()) {
scoped_tab_strip_modal_ui_.reset();
if (locked_expansion_button_) {
locked_expansion_button_->SetIsShowingNudge(false);
}
}
}
void TabStripActionContainer::OnGlicActorTaskIconClicked() {
Profile* profile = tab_strip_controller_->GetProfile();
glic::GlicKeyedServiceFactory::GetGlicKeyedService(profile)->ToggleUI(
tab_strip_controller_->GetBrowserWindowInterface(),
/*prevent_close=*/false, glic::mojom::InvocationSource::kActorTaskIcon);
if (glic_actor_task_icon_->GetIsShowingNudge()) {
// If a nudge is showing, activate the last actuated tab on click of the
// Task Icon.
if (tabs::TabInterface* last_updated_tab =
tabs::GlicActorTaskIconManagerFactory::GetForProfile(profile)
->GetLastUpdatedTab()) {
TabStripModel* tab_strip_model =
tab_strip_controller_->GetBrowserWindowInterface()
->GetTabStripModel();
int tab_index = tab_strip_model->GetIndexOfTab(last_updated_tab);
tab_strip_model->ActivateTabAt(tab_index);
}
}
actor::ui::LogTaskIconClick();
}
#endif // BUILDFLAG(ENABLE_GLIC)
void TabStripActionContainer::OnTriggerGlicNudgeUI(std::string label) {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_button_);
if (!label.empty()) {
glic_button_->SetNudgeLabel(std::move(label));
ShowTabStripNudge(glic_button_);
}
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::OnHideGlicNudgeUI() {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_button_);
HideTabStripNudge(glic_button_);
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
bool TabStripActionContainer::GetIsShowingGlicNudge() {
#if BUILDFLAG(ENABLE_GLIC)
return glic_button_ && glic_button_->GetIsShowingNudge();
#else
return false;
#endif // BUILDFLAG(ENABLE_GLIC)
}
#if BUILDFLAG(ENABLE_GLIC)
void TabStripActionContainer::TriggerGlicActorNudge(
const std::u16string nudge_text) {
CHECK(glic_actor_task_icon_);
ShowGlicActorTaskIcon();
glic_actor_task_icon_->ShowNudgeLabel(nudge_text);
HighlightGlicActorTaskIcon();
ShowTabStripNudge(glic_actor_task_icon_);
}
#endif // BUILDFLAG(ENABLE_GLIC)
void TabStripActionContainer::ShowGlicActorTaskIcon() {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_actor_button_container_);
CHECK(glic_button_);
// If the nudge is showing (ex: previous state was CheckTasks), hide the nudge
// and reset the icon
if (glic_actor_task_icon_->GetIsShowingNudge()) {
HideTabStripNudge(glic_actor_task_icon_);
glic_actor_task_icon_->SetTaskIconToDefault();
}
glic_button_ =
glic_actor_button_container_->AddChildView(std::move(glic_button_));
// When kGlicActorUiNudgeRedesign is enabled, the GlicButton should be to the
// left of the GlicActorTaskIcon.
if (features::kGlicActorUiNudgeRedesign.Get()) {
glic_actor_task_icon_->SetVisible(true);
glic_actor_button_container_->ReorderChildView(glic_button_, 0u);
}
glic_actor_button_container_->SetVisible(true);
UpdateGlicActorButtonContainerBorders();
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::HideGlicActorTaskIcon() {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_actor_button_container_);
CHECK(glic_button_);
CHECK(glic_actor_task_icon_);
if (glic_actor_task_icon_->GetIsShowingNudge()) {
HideTabStripNudge(glic_actor_task_icon_);
}
glic_actor_task_icon_->SetTaskIconToDefault();
glic_button_ = AddChildView(std::move(glic_button_));
glic_actor_button_container_->SetVisible(false);
UpdateGlicActorButtonContainerBorders();
#if !BUILDFLAG(IS_MAC)
// Re-add the separator so it's ordered after the GlicButton.
separator_ = AddChildView(std::move(separator_));
#endif // !BUILDFLAG(IS_MAC)
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
bool TabStripActionContainer::GetIsShowingGlicActorTaskIconNudge() {
#if BUILDFLAG(ENABLE_GLIC)
return glic_actor_task_icon_ && glic_actor_task_icon_->GetIsShowingNudge();
#else
return false;
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::HighlightGlicActorTaskIcon() {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_actor_task_icon_);
glic_actor_task_icon_->HighlightTaskIcon();
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::UnhighlightGlicActorTaskIcon() {
#if BUILDFLAG(ENABLE_GLIC)
CHECK(glic_actor_task_icon_);
glic_actor_task_icon_->SetDefaultColors();
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
DeclutterTriggerCTRBucket TabStripActionContainer::GetDeclutterTriggerBucket(
bool clicked) {
const auto total_tab_count =
tab_declutter_controller_->tab_strip_model()->GetTabCount();
const auto stale_tab_count = tab_declutter_controller_->GetStaleTabs().size();
if (total_tab_count < 15) {
return clicked ? DeclutterTriggerCTRBucket::kClickedUnder15Tabs
: DeclutterTriggerCTRBucket::kShownUnder15Tabs;
} else if (total_tab_count < 20) {
if (stale_tab_count < 2) {
return clicked ? DeclutterTriggerCTRBucket::kClicked15To19TabsUnder2Stale
: DeclutterTriggerCTRBucket::kShown15To19TabsUnder2Stale;
} else if (stale_tab_count < 5) {
return clicked ? DeclutterTriggerCTRBucket::kClicked15To19Tabs2To4Stale
: DeclutterTriggerCTRBucket::kShown15To19Tabs2To4Stale;
} else if (stale_tab_count < 8) {
return clicked ? DeclutterTriggerCTRBucket::kClicked15To19Tabs5To7Stale
: DeclutterTriggerCTRBucket::kShown15To19Tabs5To7Stale;
} else {
return clicked ? DeclutterTriggerCTRBucket::kClicked15To19TabsOver7Stale
: DeclutterTriggerCTRBucket::kShown15To19TabsOver7Stale;
}
} else if (total_tab_count < 25) {
if (stale_tab_count < 2) {
return clicked ? DeclutterTriggerCTRBucket::kClicked20To24TabsUnder2Stale
: DeclutterTriggerCTRBucket::kShown20To24TabsUnder2Stale;
} else if (stale_tab_count < 5) {
return clicked ? DeclutterTriggerCTRBucket::kClicked20To24Tabs2To4Stale
: DeclutterTriggerCTRBucket::kShown20To24Tabs2To4Stale;
} else if (stale_tab_count < 8) {
return clicked ? DeclutterTriggerCTRBucket::kClicked20To24Tabs5To7Stale
: DeclutterTriggerCTRBucket::kShown20To24Tabs5To7Stale;
} else {
return clicked ? DeclutterTriggerCTRBucket::kClicked20To24TabsOver7Stale
: DeclutterTriggerCTRBucket::kShown20To24TabsOver7Stale;
}
} else {
return clicked ? DeclutterTriggerCTRBucket::kClickedOver24Tabs
: DeclutterTriggerCTRBucket::kShownOver24Tabs;
}
}
void TabStripActionContainer::LogDeclutterTriggerBucket(bool clicked) {
const DeclutterTriggerCTRBucket bucket = GetDeclutterTriggerBucket(clicked);
base::UmaHistogramEnumeration(kDeclutterTriggerBucketedCTRName, bucket);
}
void TabStripActionContainer::ShowTabStripNudge(TabStripNudgeButton* button) {
if (locked_expansion_view_->IsMouseHovered()) {
SetLockedExpansionMode(LockedExpansionMode::kWillShow, button);
}
if (locked_expansion_mode_ == LockedExpansionMode::kNone) {
ExecuteShowTabStripNudge(button);
}
}
void TabStripActionContainer::HideTabStripNudge(TabStripNudgeButton* button) {
if (locked_expansion_view_->IsMouseHovered()) {
SetLockedExpansionMode(LockedExpansionMode::kWillHide, button);
}
if (locked_expansion_mode_ == LockedExpansionMode::kNone) {
ExecuteHideTabStripNudge(button);
}
}
void TabStripActionContainer::ExecuteShowTabStripNudge(
TabStripNudgeButton* button) {
if (browser_ && (button == auto_tab_group_button_) &&
!TabOrganizationUtils::GetInstance()->IsEnabled(browser_->profile())) {
return;
}
// If the tab strip already has a modal UI showing, that is not for the same
// button being hidden, exit early. If the tab strip has modal UI for the same
// button being hidden, then continue to reset the animation and start a show
// animation.
if (!tab_strip_controller_->CanShowModalUI() &&
!(animation_session_ &&
animation_session_->session_type() ==
TabStripNudgeAnimationSession::AnimationSessionType::HIDE &&
animation_session_->button() == button)) {
return;
}
button->SetIsShowingNudge(true);
#if BUILDFLAG(ENABLE_GLIC)
// Only change the margins between the GlicButton and nudges that are NOT
// coming from the GlicActorTaskIcon.
if (glic_button_ && glic_button_->GetVisible() && button != glic_button_ &&
button != glic_actor_task_icon_) {
const int space_between_buttons = kLargeSpaceBetweenButtons;
gfx::Insets margin;
margin.set_right(space_between_buttons);
button->SetProperty(views::kMarginsKey, margin);
} else {
// Reset the margins.
button->SetProperty(views::kMarginsKey, gfx::Insets());
}
#endif
scoped_tab_strip_modal_ui_.reset();
scoped_tab_strip_modal_ui_ = tab_strip_controller_->ShowModalUI();
if (!ButtonOwnsAnimation(button)) {
animation_session_ = std::make_unique<TabStripNudgeAnimationSession>(
button, this, TabStripNudgeAnimationSession::AnimationSessionType::SHOW,
base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
base::Unretained(this)),
(button != glic_button_ && button != glic_actor_task_icon_));
animation_session_->Start();
}
if (button == tab_declutter_button_) {
LogDeclutterTriggerBucket(false);
}
}
void TabStripActionContainer::ExecuteHideTabStripNudge(
TabStripNudgeButton* button) {
// Hide the current animation if the shown button is the same button. Do not
// create a new animation session.
if (animation_session_ &&
animation_session_->session_type() ==
TabStripNudgeAnimationSession::AnimationSessionType::SHOW &&
animation_session_->button() == button) {
hide_tab_strip_nudge_timer_.Stop();
animation_session_->Hide();
return;
}
if (!button->GetVisible()) {
return;
}
// Since the glic button is still visible in it's hidden state we need to have
// a special case to query if it's in its Hide state.
#if BUILDFLAG(ENABLE_GLIC)
if (button == glic_button_ && button->GetWidthFactor() == 0.0 &&
!ButtonOwnsAnimation(button)) {
return;
}
#endif // BUILDFLAG(ENABLE_GLIC)
button->SetIsShowingNudge(false);
// Stop the timer since the chip might be getting hidden on user actions like
// dismissal or click and not timeout.
hide_tab_strip_nudge_timer_.Stop();
if (!ButtonOwnsAnimation(button)) {
animation_session_ = std::make_unique<TabStripNudgeAnimationSession>(
button, this, TabStripNudgeAnimationSession::AnimationSessionType::HIDE,
base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
base::Unretained(this)),
(button != glic_button_ && button != glic_actor_task_icon_));
animation_session_->Start();
}
}
void TabStripActionContainer::SetLockedExpansionMode(
LockedExpansionMode mode,
TabStripNudgeButton* button) {
if (mode == LockedExpansionMode::kNone) {
if (locked_expansion_mode_ == LockedExpansionMode::kWillShow) {
ExecuteShowTabStripNudge(locked_expansion_button_);
} else if (locked_expansion_mode_ == LockedExpansionMode::kWillHide) {
ExecuteHideTabStripNudge(locked_expansion_button_);
}
locked_expansion_button_ = nullptr;
} else {
locked_expansion_button_ = button;
mouse_watcher_->Start(GetWidget()->GetNativeWindow());
}
locked_expansion_mode_ = mode;
}
void TabStripActionContainer::OnAutoTabGroupButtonClicked() {
base::UmaHistogramEnumeration(kAutoTabGroupsTriggerOutcomeName,
TriggerOutcome::kAccepted);
tab_organization_service_->OnActionUIAccepted(browser_);
UMA_HISTOGRAM_BOOLEAN("Tab.Organization.AllEntrypoints.Clicked", true);
UMA_HISTOGRAM_BOOLEAN("Tab.Organization.Proactive.Clicked", true);
// Force hide the button when pressed, bypassing locked expansion mode.
ExecuteHideTabStripNudge(auto_tab_group_button_);
}
void TabStripActionContainer::OnAutoTabGroupButtonDismissed() {
base::UmaHistogramEnumeration(kAutoTabGroupsTriggerOutcomeName,
TriggerOutcome::kDismissed);
tab_organization_service_->OnActionUIDismissed(browser_);
UMA_HISTOGRAM_BOOLEAN("Tab.Organization.Proactive.Clicked", false);
// Force hide the button when pressed, bypassing locked expansion mode.
ExecuteHideTabStripNudge(auto_tab_group_button_);
}
void TabStripActionContainer::OnTabStripNudgeButtonTimeout(
TabStripNudgeButton* button) {
if (button == auto_tab_group_button_) {
base::UmaHistogramEnumeration(kAutoTabGroupsTriggerOutcomeName,
TriggerOutcome::kTimedOut);
UMA_HISTOGRAM_BOOLEAN("Tab.Organization.Proactive.Clicked", false);
} else if (button == tab_declutter_button_) {
base::UmaHistogramEnumeration(kDeclutterTriggerOutcomeName,
TriggerOutcome::kTimedOut);
}
// Hide the button if not pressed. Use locked expansion mode to avoid
// disrupting the user.
HideTabStripNudge(button);
}
void TabStripActionContainer::SetupButtonProperties(
TabStripNudgeButton* button) {
// Set opacity for the button. The glic button beings opaque
button->SetOpacity(
#if BUILDFLAG(ENABLE_GLIC)
button == glic_button_ ? 1.0 : 0.0
#else
0.0
#endif // BUILDFLAG(ENABLE_GLIC)
);
}
void TabStripActionContainer::MouseMovedOutOfHost() {
SetLockedExpansionMode(LockedExpansionMode::kNone, nullptr);
}
void TabStripActionContainer::AnimationCanceled(
const gfx::Animation* animation) {
AnimationEnded(animation);
}
void TabStripActionContainer::AnimationEnded(const gfx::Animation* animation) {
animation_session_->ApplyAnimationValue(animation);
animation_session_->MarkAnimationDone(animation);
#if BUILDFLAG(ENABLE_GLIC)
if (glic_button_) {
glic_button_->OnAnimationEnded();
}
#endif
}
void TabStripActionContainer::OnAnimationSessionEnded() {
// If the button went from shown -> hidden, unblock the tab strip from
// showing other modal UIs.
if (animation_session_ &&
animation_session_->session_type() ==
TabStripNudgeAnimationSession::AnimationSessionType::HIDE) {
scoped_tab_strip_modal_ui_.reset();
if (locked_expansion_button_) {
locked_expansion_button_->SetIsShowingNudge(false);
}
}
animation_session_.reset();
}
void TabStripActionContainer::AnimationProgressed(
const gfx::Animation* animation) {
animation_session_->ApplyAnimationValue(animation);
}
void TabStripActionContainer::UpdateButtonBorders(
const gfx::Insets border_insets) {
border_insets_ = border_insets;
if (auto_tab_group_button_) {
auto_tab_group_button_->SetBorder(views::CreateEmptyBorder(border_insets));
}
if (tab_declutter_button_) {
tab_declutter_button_->SetBorder(views::CreateEmptyBorder(border_insets));
}
if (product_specifications_button_) {
product_specifications_button_->SetBorder(
views::CreateEmptyBorder(border_insets));
}
if (glic_button_) {
#if BUILDFLAG(ENABLE_GLIC)
UpdateGlicActorButtonContainerBorders();
#else
NOTREACHED();
#endif // BUILDFLAG(ENABLE_GLIC)
}
}
void TabStripActionContainer::SetGlicShowState(bool show) {
if (glic_button_) {
glic_button_->SetVisible(show);
}
if (separator_) {
separator_->SetVisible(show);
}
}
void TabStripActionContainer::SetGlicDetached(bool detached) {
#if BUILDFLAG(ENABLE_GLIC)
if (glic_button_) {
glic_button_->SetGlicDetached(detached);
}
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::SetGlicPanelIsOpen(bool open) {
#if BUILDFLAG(ENABLE_GLIC)
if (glic_button_) {
glic_button_->SetGlicPanelIsOpen(open);
}
#endif // BUILDFLAG(ENABLE_GLIC)
}
void TabStripActionContainer::DidBecomeActive(BrowserWindowInterface* browser) {
separator_->SetColorId(kColorTabDividerFrameActive);
}
void TabStripActionContainer::DidBecomeInactive(
BrowserWindowInterface* browser) {
separator_->SetColorId(kColorTabDividerFrameInactive);
}
bool TabStripActionContainer::ButtonOwnsAnimation(
const TabStripNudgeButton* button) const {
#if BUILDFLAG(ENABLE_GLIC)
return button == glic_button_ &&
base::FeatureList::IsEnabled(features::kGlicEntrypointVariations);
#else
return false;
#endif
}
BEGIN_METADATA(TabStripActionContainer)
END_METADATA