910e62b5创建于 1月15日历史提交
// Copyright 2013 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/toolbar/browser_app_menu_button.h"

#include <set>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_otr_state.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/toolbar/app_menu_icon_controller.h"
#include "chrome/browser/ui/toolbar/app_menu_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/toolbar/app_menu.h"
#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/browser/user_education/tutorial_identifiers.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/user_education/common/feature_promo/feature_promo_controller.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/metrics.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/virtual_keyboard_controller.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace {
constexpr int kChromeRefreshImageLabelPadding = 2;
}

// static
bool BrowserAppMenuButton::g_open_app_immediately_for_testing = false;

BrowserAppMenuButton::BrowserAppMenuButton(ToolbarView* toolbar_view)
    : AppMenuButton(base::BindRepeating(&BrowserAppMenuButton::ButtonPressed,
                                        base::Unretained(this))),
      toolbar_view_(toolbar_view) {
  SetHorizontalAlignment(gfx::ALIGN_RIGHT);
  SetImageLabelSpacing(kChromeRefreshImageLabelPadding);
  label()->SetPaintToLayer();
  label()->SetSkipSubpixelRenderingOpacityCheck(true);
  label()->layer()->SetFillsBoundsOpaquely(false);
  label()->SetSubpixelRenderingEnabled(false);
}

BrowserAppMenuButton::~BrowserAppMenuButton() = default;

void BrowserAppMenuButton::SetTypeAndSeverity(
    AppMenuIconController::TypeAndSeverity type_and_severity) {
  type_and_severity_ = type_and_severity;
  UpdateThemeBasedState();
}

void BrowserAppMenuButton::ShowMenu(int run_types) {
  if (IsMenuShowing()) {
    return;
  }

#if BUILDFLAG(IS_CHROMEOS)
  if (auto* input_method = GetInputMethod()) {
    if (auto* controller = input_method->GetVirtualKeyboardController();
        controller && controller->IsKeyboardVisible()) {
      input_method->SetVirtualKeyboardVisibilityIfEnabled(false);
    }
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  Browser* browser = toolbar_view_->browser();

  // Allow highlighting menu items when the menu was opened while
  // certain tutorials are running.
  AlertMenuItem alert_item = GetAlertItemForRunningTutorial();

  RunMenu(std::make_unique<AppMenuModel>(
              toolbar_view_, browser, toolbar_view_->app_menu_icon_controller(),
              alert_item),
          browser, run_types);
}

AlertMenuItem BrowserAppMenuButton::GetAlertItemForRunningTutorial() {
  Browser* browser = toolbar_view_->browser();
  BrowserWindow* browser_window = browser->window();

  if (browser_window == nullptr) {
    return AlertMenuItem::kNone;
  }

  auto* const service =
      UserEducationServiceFactory::GetForBrowserContext(browser->profile());
  if (service && service->tutorial_service().IsRunningTutorial(
                     kPasswordManagerTutorialId)) {
    return AlertMenuItem::kPasswordManager;
  }

  return AlertMenuItem::kNone;
}

void BrowserAppMenuButton::OnThemeChanged() {
  UpdateThemeBasedState();
  AppMenuButton::OnThemeChanged();
}

void BrowserAppMenuButton::UpdateThemeBasedState() {
  UpdateLayoutInsets();
  UpdateTextAndHighlightColor();
  // Call `UpdateIcon()` after `UpdateTextAndHighlightColor()` as the icon color
  // depends on if the container is in an expanded state.
  UpdateIcon();
  UpdateInkdrop();
  // Outset focus ring should be present for the chip but not when only
  // the icon is visible.
  views::FocusRing::Get(this)->SetOutsetFocusRingDisabled(
      !IsLabelPresentAndVisible());
}

void BrowserAppMenuButton::UpdateIcon() {
  const gfx::VectorIcon& icon = ui::TouchUiController::Get()->touch_ui()
                                    ? kBrowserToolsTouchIcon
                                    : kBrowserToolsChromeRefreshIcon;
  for (auto state : kButtonStates) {
    SkColor icon_color = GetForegroundColor(state);
    SetImageModel(state, ui::ImageModel::FromVectorIcon(icon, icon_color));
  }
}

void BrowserAppMenuButton::UpdateInkdrop() {
  if (IsLabelPresentAndVisible()) {
    ConfigureToolbarInkdropForRefresh2023(this, kColorAppMenuChipInkDropHover,
                                          kColorAppMenuChipInkDropRipple);
  } else {
    ConfigureToolbarInkdropForRefresh2023(this, kColorToolbarInkDropHover,
                                          kColorToolbarInkDropRipple);
  }
}

bool BrowserAppMenuButton::IsLabelPresentAndVisible() const {
  if (!label()) {
    return false;
  }
  return label()->GetVisible() && !label()->GetText().empty();
}

SkColor BrowserAppMenuButton::GetForegroundColor(ButtonState state) const {
  if (IsLabelPresentAndVisible()) {
    const auto* const color_provider = GetColorProvider();
    if (type_and_severity_.use_primary_colors) {
      return color_provider->GetColor(kColorAppMenuExpandedForegroundPrimary);
    }
    return color_provider->GetColor(kColorAppMenuExpandedForegroundDefault);
  }

  return ToolbarButton::GetForegroundColor(state);
}

void BrowserAppMenuButton::UpdateTextAndHighlightColor() {
  int tooltip_message_id;
  std::u16string text;
  if (type_and_severity_.severity == AppMenuIconController::Severity::kNone) {
    tooltip_message_id = IDS_APPMENU_TOOLTIP;
  } else if (type_and_severity_.type ==
             AppMenuIconController::IconType::kUpgradeNotification) {
    tooltip_message_id = IDS_APPMENU_TOOLTIP_UPDATE_AVAILABLE;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && \
    (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX))
    int message_id = IDS_APP_MENU_BUTTON_UPDATE;
    // Select an update text option randomly. Show this text in all browser
    // windows.
    static const int update_text_option = base::RandInt(1, 3);
    if (update_text_option == 1) {
      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT1;
    } else if (update_text_option == 2) {
      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT2;
    } else {
      message_id = IDS_APP_MENU_BUTTON_UPDATE_ALT3;
    }
    text = l10n_util::GetStringUTF16(message_id);
#else
    text = l10n_util::GetStringUTF16(IDS_APP_MENU_BUTTON_UPDATE);
#endif
  } else {
    tooltip_message_id = IDS_APPMENU_TOOLTIP_ALERT;
    const int text_id =
        type_and_severity_.severity == AppMenuIconController::Severity::kLow
            ? IDS_APP_MENU_BUTTON_ACTION_REQUIRED
            : IDS_APP_MENU_BUTTON_ERROR;
    text = l10n_util::GetStringUTF16(text_id);
  }

  SetTooltipText(l10n_util::GetStringUTF16(tooltip_message_id));
  SetHighlight(text, GetHighlightColor());
}

bool BrowserAppMenuButton::ShouldPaintBorder() const {
  return false;
}

void BrowserAppMenuButton::UpdateLayoutInsets() {
  if (IsLabelPresentAndVisible()) {
    SetLayoutInsets(::GetLayoutInsets(BROWSER_APP_MENU_CHIP_PADDING));
  } else {
    SetLayoutInsets(::GetLayoutInsets(TOOLBAR_BUTTON));
  }
}

std::optional<SkColor> BrowserAppMenuButton::GetHighlightTextColor() const {
  if (IsLabelPresentAndVisible()) {
    const auto* const color_provider = GetColorProvider();
    if (type_and_severity_.use_primary_colors) {
      return color_provider->GetColor(kColorAppMenuExpandedForegroundPrimary);
    }
    return color_provider->GetColor(kColorAppMenuExpandedForegroundDefault);
  }
  return std::nullopt;
}

std::optional<SkColor> BrowserAppMenuButton::GetHighlightColor() const {
  const auto* const color_provider = GetColorProvider();
  if (type_and_severity_.severity == AppMenuIconController::Severity::kNone) {
    return std::nullopt;
  } else {
    return color_provider->GetColor(type_and_severity_.use_primary_colors
                                        ? kColorAppMenuHighlightPrimary
                                        : kColorAppMenuHighlightDefault);
  }
}

void BrowserAppMenuButton::OnTouchUiChanged() {
  UpdateColorsAndInsets();
  PreferredSizeChanged();
}

void BrowserAppMenuButton::ButtonPressed(const ui::Event& event) {
#if BUILDFLAG(IS_CHROMEOS)
  auto* const user_education =
      BrowserUserEducationInterface::From(toolbar_view_->browser());
  if (user_education->IsFeaturePromoActive(
          feature_engagement::kIPHPasswordsSavePrimingPromoFeature)) {
    user_education->NotifyFeaturePromoFeatureUsed(
        feature_engagement::kIPHPasswordsSavePrimingPromoFeature,
        FeaturePromoFeatureUsedAction::kClosePromoIfPresent);
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  ShowMenu(event.IsKeyEvent() ? (views::MenuRunner::SHOULD_SHOW_MNEMONICS |
                                 views::MenuRunner::INVOKED_FROM_KEYBOARD)
                              : views::MenuRunner::NO_FLAGS);
}

bool BrowserAppMenuButton::HandleAccessibleAction(
    const ui::AXActionData& action_data) {
  if (action_data.action == ax::mojom::Action::kExpand) {
    ShowMenu(views::MenuRunner::NO_FLAGS);
    return true;
  }
  if (action_data.action == ax::mojom::Action::kCollapse) {
    if (AppMenuButton::IsMenuShowing()) {
      CloseMenu();
    }
    return true;
  }
  return AppMenuButton::HandleAccessibleAction(action_data);
}

BEGIN_METADATA(BrowserAppMenuButton)
END_METADATA