blob: 106c04cac2553f979fa29e903744fe9a75177404 [file] [log] [blame]
// Copyright 2023 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/autofill/popup/popup_cell_utils.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "cc/paint/skia_paint_canvas.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/views/autofill/payments/payments_view_util.h"
#include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/webid/identity_ui_utils.h"
#include "chrome/grit/platform_locale_settings.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/browser/ui/autofill_resource_utils.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/password_manager/core/common/password_manager_constants.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/image_model_utils.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/monogram_utils.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/layout/table_layout.h"
#include "ui/views/layout/table_layout_view.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "components/plus_addresses/core/browser/resources/vector_icons.h"
#endif
namespace autofill::popup_cell_utils {
namespace {
// The default icon size used in the suggestion drop down.
constexpr int kIconSize = 16;
constexpr int kPersonCheckIconSize = 20;
constexpr int kRecoveryPasswordIconSize = 20;
constexpr int kChromeRefreshIconSize = 20;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
constexpr int kGooglePayLogoWidth = 40;
// The icon size used in the suggestion dropdown for displaying the Google
// Wallet icon in the "Manage loyalty cards" entry and in all loyalty cards
// submenu entries.
constexpr int kGoogleWalletIconSize = 20;
#endif
// The additional height of the row in case it has two lines of text.
constexpr int kAutofillPopupAdditionalDoubleRowHeight = 16;
// The additional height of the row in case it has three lines of text.
constexpr int kAutofillPopupAdditionalTripleRowHeight = 24;
// The additional padding of the row in case it has three lines of text.
constexpr int kAutofillPopupAdditionalVerticalPadding = 16;
// Vertical spacing between labels in one row.
constexpr int kAdjacentLabelsVerticalSpacing = 2;
// The icon size used in the suggestion dropdown for displaying the Google
// Password Manager icon in the Manager Passwords entry.
constexpr int kGooglePasswordManagerIconSize = 20;
// Metric to measure the duration of getting the image for the Autofill pop-up.
constexpr char kHistogramGetImageViewByName[] =
"Autofill.PopupGetImageViewTime";
// The monochrome icon size used when rendering letter monochrome icons.
constexpr int kMonochromeIconSize = 24;
// The background color of the letter monochrome icons.
constexpr SkColor kMonochromeIconBgColor = SkColorSetARGB(255, 237, 242, 250);
// The text color of the letter monochrome icons.
constexpr SkColor kMonochromeIconTextColor = SkColorSetARGB(255, 71, 71, 71);
// Returns the name of the network for payment method icons, for home/work
// address a11y labels and empty string otherwise.
std::u16string GetIconAccessibleName(Suggestion::Icon icon) {
switch (icon) {
case Suggestion::Icon::kCardAmericanExpress:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX);
case Suggestion::Icon::kCardDiners:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS);
case Suggestion::Icon::kCardDiscover:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER);
case Suggestion::Icon::kCardElo:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_ELO);
case Suggestion::Icon::kCardJCB:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB);
case Suggestion::Icon::kCardMasterCard:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD);
case Suggestion::Icon::kCardMir:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MIR);
case Suggestion::Icon::kCardTroy:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_TROY);
case Suggestion::Icon::kCardUnionPay:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_UNION_PAY);
case Suggestion::Icon::kCardVerve:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VERVE);
case Suggestion::Icon::kCardVisa:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA);
// Other networks.
case Suggestion::Icon::kCardGeneric:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GENERIC);
case Suggestion::Icon::kIban:
return l10n_util::GetStringUTF16(IDS_AUTOFILL_IBAN_GENERIC);
case Suggestion::Icon::kHome:
return l10n_util::GetStringUTF16(
IDS_AUTOFILL_HOME_PROFILE_ICON_ACCESSIBILITY_LABEL);
case Suggestion::Icon::kWork:
return l10n_util::GetStringUTF16(
IDS_AUTOFILL_WORK_PROFILE_ICON_ACCESSIBILITY_LABEL);
case Suggestion::Icon::kAccount:
case Suggestion::Icon::kBnpl:
case Suggestion::Icon::kClear:
case Suggestion::Icon::kCode:
case Suggestion::Icon::kDelete:
case Suggestion::Icon::kDevice:
case Suggestion::Icon::kVehicle:
case Suggestion::Icon::kEdit:
case Suggestion::Icon::kEmail:
case Suggestion::Icon::kError:
case Suggestion::Icon::kFlight:
case Suggestion::Icon::kGlobe:
case Suggestion::Icon::kGoogle:
case Suggestion::Icon::kGoogleMonochrome:
case Suggestion::Icon::kGooglePasswordManager:
case Suggestion::Icon::kGooglePay:
case Suggestion::Icon::kGoogleWallet:
case Suggestion::Icon::kGoogleWalletMonochrome:
case Suggestion::Icon::kIdCard:
case Suggestion::Icon::kKey:
case Suggestion::Icon::kLocation:
case Suggestion::Icon::kLoyalty:
case Suggestion::Icon::kMagic:
case Suggestion::Icon::kNoIcon:
case Suggestion::Icon::kOfferTag:
case Suggestion::Icon::kPenSpark:
case Suggestion::Icon::kPersonCheck:
case Suggestion::Icon::kPlusAddress:
case Suggestion::Icon::kQuestionMark:
case Suggestion::Icon::kRecoveryPassword:
case Suggestion::Icon::kSaveAndFill:
case Suggestion::Icon::kScanCreditCard:
case Suggestion::Icon::kSettings:
case Suggestion::Icon::kUndo:
case Suggestion::Icon::kAndroidMessages:
return std::u16string();
}
NOTREACHED();
}
std::optional<ui::ImageModel> ImageModelFromImageSkia(
const gfx::ImageSkia& image_skia) {
if (image_skia.isNull()) {
return std::nullopt;
}
auto image_model = ui::ImageModel::FromImageSkia(image_skia);
return image_model;
}
// Converts the `image_model` to an `ImageView`. If `apply_deactivated_style` is
// true, the image will be converted to a disabled image.
std::unique_ptr<views::ImageView> ConvertModelToImageView(
std::optional<ui::ImageModel> image_model,
bool apply_deactivated_style) {
if (!image_model) {
return nullptr;
}
if (apply_deactivated_style) {
image_model = ui::GetDefaultDisabledIconFromImageModel(
image_model.value(),
ui::ColorProviderManager::Get().GetColorProviderFor(
ui::NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
nullptr)));
}
return std::make_unique<views::ImageView>(image_model.value());
}
// Creates the table in which all the Autofill suggestion content apart from
// leading and trailing icons is contained.
std::unique_ptr<views::TableLayoutView> CreateSuggestionContentTable(
std::unique_ptr<views::Label> main_text_label,
std::vector<std::unique_ptr<views::View>> minor_text_labels,
std::unique_ptr<views::Label> description_label,
std::vector<std::unique_ptr<views::View>> subtext_views,
bool align_description_label_to_right) {
const bool has_two_columns = !!description_label;
auto table =
views::Builder<views::TableLayoutView>()
.AddColumn(views::LayoutAlignment::kStart,
views::LayoutAlignment::kStretch,
views::TableLayout::kFixedSize,
views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
.Build();
if (has_two_columns) {
const views::LayoutAlignment kHorizontalAlignment =
align_description_label_to_right ? views::LayoutAlignment::kEnd
: views::LayoutAlignment::kStart;
const float kHorizontalResize = align_description_label_to_right
? 1.0f
: views::TableLayout::kFixedSize;
const int kDividerSpacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL_LIST);
table->AddPaddingColumn(views::TableLayout::kFixedSize, kDividerSpacing);
table->AddColumn(kHorizontalAlignment, views::LayoutAlignment::kStretch,
kHorizontalResize,
views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
}
// Major and minor text go into the first row, first column.
table->AddRows(1, 0);
if (!minor_text_labels.empty()) {
auto first_line_container = std::make_unique<views::View>();
first_line_container
->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetMainAxisAlignment(views::LayoutAlignment::kStart)
.SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCollapseMargins(true)
.SetDefault(
views::kMarginsKey,
gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL_LIST)));
first_line_container->AddChildView(std::move(main_text_label));
for (auto& minor_text : minor_text_labels) {
first_line_container->AddChildView(std::move(minor_text));
}
table->AddChildView(std::move(first_line_container));
} else {
table->AddChildView(std::move(main_text_label));
}
// The description goes into the first row, second column.
if (has_two_columns) {
table->AddChildView(std::move(description_label));
}
// Every subtext label goes into an additional row.
for (std::unique_ptr<views::View>& subtext_view : subtext_views) {
table->AddPaddingRow(0, kAdjacentLabelsVerticalSpacing).AddRows(1, 0);
table->AddChildView(std::move(subtext_view));
if (has_two_columns) {
table->AddChildView(std::make_unique<views::View>());
}
}
return table;
}
bool IsPaymentMethodSuggestion(const Suggestion& suggestion) {
switch (suggestion.type) {
case SuggestionType::kCreditCardEntry:
case SuggestionType::kVirtualCreditCardEntry:
case SuggestionType::kIbanEntry:
case SuggestionType::kBnplEntry:
case SuggestionType::kSaveAndFillCreditCardEntry:
return true;
case SuggestionType::kAllLoyaltyCardsEntry:
case SuggestionType::kAllSavedPasswordsEntry:
case SuggestionType::kFreeformFooter:
case SuggestionType::kManageAddress:
case SuggestionType::kManageAutofillAi:
case SuggestionType::kManageCreditCard:
case SuggestionType::kManageIban:
case SuggestionType::kManageLoyaltyCard:
case SuggestionType::kManagePlusAddress:
case SuggestionType::kScanCreditCard:
case SuggestionType::kSeePromoCodeDetails:
case SuggestionType::kUndoOrClear:
case SuggestionType::kViewPasswordDetails:
case SuggestionType::kPendingStateSignin:
case SuggestionType::kAccountStoragePasswordEntry:
case SuggestionType::kAddressEntry:
case SuggestionType::kAddressEntryOnTyping:
case SuggestionType::kAddressFieldByFieldFilling:
case SuggestionType::kAutocompleteEntry:
case SuggestionType::kComposeResumeNudge:
case SuggestionType::kComposeProactiveNudge:
case SuggestionType::kComposeDisable:
case SuggestionType::kComposeGoToSettings:
case SuggestionType::kComposeNeverShowOnThisSiteAgain:
case SuggestionType::kComposeSavedStateNotification:
case SuggestionType::kDatalistEntry:
case SuggestionType::kDevtoolsTestAddressByCountry:
case SuggestionType::kDevtoolsTestAddressEntry:
case SuggestionType::kDevtoolsTestAddresses:
case SuggestionType::kFillExistingPlusAddress:
case SuggestionType::kFillPassword:
case SuggestionType::kGeneratePasswordEntry:
case SuggestionType::kInsecureContextPaymentDisabledMessage:
case SuggestionType::kLoyaltyCardEntry:
case SuggestionType::kMerchantPromoCodeEntry:
case SuggestionType::kMixedFormMessage:
case SuggestionType::kPasswordEntry:
case SuggestionType::kBackupPasswordEntry:
case SuggestionType::kTroubleSigningInEntry:
case SuggestionType::kPasswordFieldByFieldFilling:
case SuggestionType::kSeparator:
case SuggestionType::kTitle:
case SuggestionType::kIdentityCredential:
case SuggestionType::kWebauthnCredential:
case SuggestionType::kFillAutofillAi:
case SuggestionType::kOneTimePasswordEntry:
case SuggestionType::kWebauthnSignInWithAnotherDevice:
return false;
}
}
} // namespace
std::optional<ui::ImageModel> GetIconImageModelFromIcon(Suggestion::Icon icon) {
switch (icon) {
case Suggestion::Icon::kNoIcon:
return std::nullopt;
case Suggestion::Icon::kHome:
return ImageModelFromVectorIcon(vector_icons::kHomeIcon, kIconSize);
case Suggestion::Icon::kWork:
return ImageModelFromVectorIcon(vector_icons::kWorkIcon, kIconSize);
case Suggestion::Icon::kAccount:
return ImageModelFromVectorIcon(kAccountCircleIcon, kIconSize);
case Suggestion::Icon::kClear:
return ImageModelFromVectorIcon(kBackspaceIcon, kIconSize);
case Suggestion::Icon::kCode:
return ImageModelFromVectorIcon(vector_icons::kCodeIcon, kIconSize);
case Suggestion::Icon::kDelete:
return ImageModelFromVectorIcon(kTrashCanRefreshIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kDevice:
return ImageModelFromVectorIcon(kDevicesIcon, kIconSize);
case Suggestion::Icon::kVehicle:
return ImageModelFromVectorIcon(vector_icons::kDirectionsCarIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kEdit:
return ImageModelFromVectorIcon(vector_icons::kEditChromeRefreshIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kEmail:
return ImageModelFromVectorIcon(vector_icons::kEmailOutlineIcon,
kIconSize);
case Suggestion::Icon::kError:
return ui::ImageModel::FromVectorIcon(vector_icons::kErrorIcon,
ui::kColorSysError, kIconSize);
case Suggestion::Icon::kFlight:
return ImageModelFromVectorIcon(vector_icons::kFlightIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kGlobe:
return ImageModelFromVectorIcon(kGlobeIcon, kIconSize);
case Suggestion::Icon::kGoogle:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromImageSkia(gfx::CreateVectorIcon(
vector_icons::kGoogleGLogoIcon, kIconSize, gfx::kPlaceholderColor));
#else
return std::nullopt;
#endif
case Suggestion::Icon::kGoogleMonochrome:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromVectorIcon(vector_icons::kGoogleGLogoMonochromeIcon,
kIconSize);
#else
return ImageModelFromVectorIcon(vector_icons::kEmailIcon, kIconSize);
#endif
case Suggestion::Icon::kIdCard:
return ImageModelFromVectorIcon(vector_icons::kIdCardIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kKey:
return ImageModelFromVectorIcon(kKeyIcon, kIconSize);
case Suggestion::Icon::kLocation:
return ImageModelFromVectorIcon(
vector_icons::kLocationOnChromeRefreshIcon, kChromeRefreshIconSize);
case Suggestion::Icon::kLoyalty:
return ImageModelFromVectorIcon(vector_icons::kLoyaltyIcon,
kChromeRefreshIconSize);
case Suggestion::Icon::kMagic:
return ImageModelFromVectorIcon(vector_icons::kMagicButtonIcon,
kIconSize);
case Suggestion::Icon::kPenSpark:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromVectorIcon(vector_icons::kPenSparkIcon, kIconSize);
#else
return ImageModelFromVectorIcon(vector_icons::kEditIcon, kIconSize);
#endif
case Suggestion::Icon::kPersonCheck:
return ImageModelFromVectorIcon(vector_icons::kPersonCheckIcon,
kPersonCheckIconSize);
case Suggestion::Icon::kPlusAddress:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromVectorIcon(plus_addresses::kPlusAddressLogoSmallIcon,
kIconSize);
#else
return ImageModelFromVectorIcon(vector_icons::kEmailIcon, kIconSize);
#endif
case Suggestion::Icon::kQuestionMark:
return ImageModelFromVectorIcon(vector_icons::kHelpOutlineIcon,
kRecoveryPasswordIconSize);
case Suggestion::Icon::kRecoveryPassword:
return ImageModelFromVectorIcon(vector_icons::kHistoryChromeRefreshIcon,
kRecoveryPasswordIconSize);
case Suggestion::Icon::kSaveAndFill:
return ImageModelFromVectorIcon(kCreditCardIcon, kIconSize);
case Suggestion::Icon::kSettings:
return ImageModelFromVectorIcon(omnibox::kProductIcon, kIconSize);
case Suggestion::Icon::kUndo:
return ImageModelFromVectorIcon(vector_icons::kUndoIcon, kIconSize);
case Suggestion::Icon::kGooglePasswordManager:
return ImageModelFromVectorIcon(GooglePasswordManagerVectorIcon(),
kGooglePasswordManagerIconSize);
case Suggestion::Icon::kGooglePay:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ui::ImageModel::FromImageGenerator(
base::BindRepeating(&CreateTiledGooglePayLogo, kGooglePayLogoWidth,
kIconSize),
gfx::Size(kGooglePayLogoWidth, kIconSize));
#else
return ImageModelFromVectorIcon(kCreditCardIcon, kIconSize);
#endif
case Suggestion::Icon::kGoogleWallet:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromVectorIcon(vector_icons::kGoogleWalletIcon,
kGoogleWalletIconSize);
#else
return std::nullopt;
#endif
case Suggestion::Icon::kGoogleWalletMonochrome:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return ImageModelFromVectorIcon(vector_icons::kGoogleWalletMonochromeIcon,
kGoogleWalletIconSize);
#else
return std::nullopt;
#endif
case Suggestion::Icon::kIban:
case Suggestion::Icon::kOfferTag:
case Suggestion::Icon::kScanCreditCard:
case Suggestion::Icon::kCardGeneric:
case Suggestion::Icon::kCardAmericanExpress:
case Suggestion::Icon::kCardDiners:
case Suggestion::Icon::kCardDiscover:
case Suggestion::Icon::kCardElo:
case Suggestion::Icon::kCardJCB:
case Suggestion::Icon::kCardMasterCard:
case Suggestion::Icon::kCardMir:
case Suggestion::Icon::kCardTroy:
case Suggestion::Icon::kCardUnionPay:
case Suggestion::Icon::kCardVerve:
case Suggestion::Icon::kCardVisa:
case Suggestion::Icon::kBnpl:
case Suggestion::Icon::kAndroidMessages: {
// For other suggestion entries, get the icon from PNG files.
int icon_id = GetIconResourceID(icon);
DCHECK_NE(icon_id, 0);
return ImageModelFromImageSkia(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(icon_id));
}
}
NOTREACHED();
}
std::u16string GetVoiceOverStringFromSuggestion(const Suggestion& suggestion) {
if (suggestion.voice_over) {
return *suggestion.voice_over;
}
std::vector<std::u16string> text;
auto add_if_not_empty = [&text](std::u16string value) {
if (!value.empty()) {
text.push_back(std::move(value));
}
};
add_if_not_empty(GetIconAccessibleName(suggestion.icon));
text.push_back(suggestion.main_text.value);
const bool is_vcn =
suggestion.type == SuggestionType::kVirtualCreditCardEntry;
const bool is_iban = suggestion.type == SuggestionType::kIbanEntry;
const bool has_subtext =
!suggestion.labels.empty() && !suggestion.labels[0][0].value.empty();
std::u16string badge_text;
if (is_vcn) {
badge_text = l10n_util::GetStringUTF16(
IDS_AUTOFILL_VIRTUAL_CARD_SUGGESTION_OPTION_VALUE);
} else if (is_iban) {
badge_text =
l10n_util::GetStringUTF16(IDS_AUTOFILL_IBAN_SUGGESTION_OPTION_VALUE);
}
// A badge is applied as a label view for the following cases:
// - A virtual card that does not have a product description or nickname.
// - An IBAN that does not have a nickname.
bool badge_used_as_minor_text = false;
if ((is_vcn && !suggestion.minor_texts.empty()) ||
(is_iban && !has_subtext)) {
add_if_not_empty(badge_text);
badge_used_as_minor_text = true;
} else if (!suggestion.minor_texts.empty()) {
std::vector<std::u16string> text_values;
for (const auto& minor_text : suggestion.minor_texts) {
text_values.push_back(minor_text.value);
}
std::u16string sublabel = base::JoinString(text_values, u" ");
add_if_not_empty(sublabel);
}
bool badge_added_to_labels = false;
for (const std::vector<Suggestion::Text>& row : suggestion.labels) {
std::vector<std::u16string> row_values;
for (const Suggestion::Text& label : row) {
// `label_text` is not populated for footers or autocomplete entries.
if (!label.value.empty()) {
row_values.push_back(label.value);
}
}
// If a badge is present and not used as minor text, apply it as a
// label view for these specific cases:
// - Virtual card that has a product description or nickname.
// - IBAN that has a nickname.
if (!badge_text.empty() && !badge_used_as_minor_text &&
!badge_added_to_labels) {
row_values.push_back(badge_text);
badge_added_to_labels = true;
}
add_if_not_empty(base::JoinString(row_values, u" "));
}
// `additional_label` is only populated in a passwords context.
add_if_not_empty(suggestion.additional_label);
return base::JoinString(text, u" ");
}
std::unique_ptr<views::ImageView> GetIconImageView(
const Suggestion& suggestion) {
base::TimeTicks start_time = base::TimeTicks::Now();
// Check that icon and custom_icon are not set at the same time.
CHECK(!(suggestion.icon != Suggestion::Icon::kNoIcon &&
suggestion.custom_icon.index() != 0));
if (auto* monochrome_icon = std::get_if<Suggestion::LetterMonochromeIcon>(
&suggestion.custom_icon)) {
SkBitmap bitmap;
bitmap.allocN32Pixels(kMonochromeIconSize, kMonochromeIconSize, false);
cc::SkiaPaintCanvas paint_canvas(bitmap);
gfx::Canvas canvas(&paint_canvas, 1.f);
const std::vector<std::string> font_names = {
l10n_util::GetStringUTF8(IDS_NTP_FONT_FAMILY)};
gfx::DrawMonogramInCanvas(&canvas, kMonochromeIconSize, kMonochromeIconSize,
monochrome_icon->monogram_text, font_names,
kMonochromeIconTextColor, kMonochromeIconBgColor);
return ConvertModelToImageView(
ImageModelFromImageSkia(
gfx::Image::CreateFrom1xBitmap(bitmap).AsImageSkia()),
suggestion.HasDeactivatedStyle());
}
if (auto* image = std::get_if<gfx::Image>(&suggestion.custom_icon);
image && !image->IsEmpty()) {
gfx::ImageSkia image_skia = image->AsImageSkia();
if (std::holds_alternative<Suggestion::IdentityCredentialPayload>(
suggestion.payload)) {
image_skia = webid::CreateCircleCroppedImage(
image_skia, webid::kDesiredAvatarSizeInAutofillDropdown);
}
return ConvertModelToImageView(ImageModelFromImageSkia(image_skia),
suggestion.HasDeactivatedStyle());
}
std::unique_ptr<views::ImageView> icon_image_view =
ConvertModelToImageView(GetIconImageModelFromIcon(suggestion.icon),
suggestion.HasDeactivatedStyle());
base::UmaHistogramTimes(kHistogramGetImageViewByName,
base::TimeTicks::Now() - start_time);
if (icon_image_view) {
// It is possible to have icons of different sizes (kChromeRefreshIconSize
// and kIconSize) on the same popup. Setting the icon view width to
// the largest value ensures that the icon occupies consistent horizontal
// space and makes icons (and the text after them) aligned. It expands
// the area of kIconSize icons only and doesn't change those that are bigger
// by design (e.g. payment card icons) and have no alignment issues.
gfx::Size size = icon_image_view->GetPreferredSize();
size.set_width(std::max(kChromeRefreshIconSize, size.width()));
icon_image_view->SetPreferredSize(size);
}
return icon_image_view;
}
std::unique_ptr<views::ImageView> GetTrailingIconImageView(
const Suggestion& suggestion) {
base::TimeTicks start_time = base::TimeTicks::Now();
std::optional<ui::ImageModel> image_model =
GetIconImageModelFromIcon(suggestion.trailing_icon);
std::unique_ptr<views::ImageView> icon_image_view =
ConvertModelToImageView(image_model, suggestion.HasDeactivatedStyle());
base::UmaHistogramTimes(kHistogramGetImageViewByName,
base::TimeTicks::Now() - start_time);
return icon_image_view;
}
void AddSpacerWithSize(views::BoxLayoutView& view,
int spacer_width,
bool resize) {
auto spacer = views::Builder<views::View>()
.SetPreferredSize(gfx::Size(spacer_width, 1))
.Build();
view.SetFlexForView(view.AddChildView(std::move(spacer)),
/*flex=*/resize ? 1 : 0,
/*use_min_size=*/true);
}
void AddSuggestionContentToView(
const Suggestion& suggestion,
std::unique_ptr<views::Label> main_text_label,
std::vector<std::unique_ptr<views::View>> minor_text_labels,
std::unique_ptr<views::Label> description_label,
std::vector<std::unique_ptr<views::View>> subtext_views,
std::unique_ptr<views::View> icon,
PopupRowContentView& content_view) {
bool should_show_new_fop_format =
base::FeatureList::IsEnabled(
autofill::features::kAutofillEnableNewFopDisplayDesktop) &&
IsPaymentMethodSuggestion(suggestion);
// Adjust the row height based on the number of subtexts (lines of text).
int row_height = views::MenuConfig::instance().touchable_menu_height;
if (!subtext_views.empty() || should_show_new_fop_format) {
row_height += kAutofillPopupAdditionalDoubleRowHeight;
}
// If there are three rows in total, add extra padding to avoid cramming.
DCHECK_LE(subtext_views.size(), 2u);
if (subtext_views.size() == 2u) {
if (should_show_new_fop_format) {
row_height += kAutofillPopupAdditionalTripleRowHeight;
} else {
content_view.SetInsideBorderInsets(
gfx::Insets(content_view.GetInsideBorderInsets())
.set_top_bottom(kAutofillPopupAdditionalVerticalPadding,
kAutofillPopupAdditionalVerticalPadding));
}
}
content_view.SetMinimumCrossAxisSize(row_height);
// The leading icon.
if (suggestion.is_loading) {
views::Throbber* throbber =
content_view.AddChildView(std::make_unique<views::Throbber>());
if (icon) {
// Prevent that the layout is shifted when transitioning from throbber
// to icon and vice versa when there is a width difference.
const int size_delta =
icon->GetMinimumSize().width() - throbber->GetMinimumSize().width();
throbber->SetProperty(views::kMarginsKey,
gfx::Insets::VH(0, std::max(size_delta, 0) / 2));
}
throbber->Start();
AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
/*resize=*/false);
content_view.SetEnabled(false);
} else if (icon) {
content_view.AddChildView(std::move(icon));
AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
/*resize=*/false);
}
// The actual content table.
content_view.SetFlexForView(
content_view.AddChildView(CreateSuggestionContentTable(
std::move(main_text_label), std::move(minor_text_labels),
std::move(description_label), std::move(subtext_views),
suggestion.additional_label_alignment_right)),
1);
// The trailing icon.
if (std::unique_ptr<views::ImageView> trailing_icon =
GetTrailingIconImageView(suggestion)) {
AddSpacerWithSize(content_view, PopupBaseView::ArrowHorizontalMargin(),
/*resize=*/true);
content_view.AddChildView(std::move(trailing_icon));
}
// Force a refresh to ensure all the labels'styles are correct.
content_view.UpdateStyle(/*selected=*/false);
}
ui::ImageModel ImageModelFromVectorIcon(const gfx::VectorIcon& vector_icon,
int icon_size = kIconSize) {
return ui::ImageModel::FromVectorIcon(vector_icon, ui::kColorIcon, icon_size);
}
const gfx::VectorIcon& GetExpandableMenuIcon(SuggestionType type) {
CHECK(IsExpandableSuggestionType(type));
// Only compose suggestions have a different expandable icon.
return GetFillingProductFromSuggestionType(type) == FillingProduct::kCompose
? kBrowserToolsChromeRefreshIcon
: vector_icons::kSubmenuArrowChromeRefreshIcon;
}
} // namespace autofill::popup_cell_utils