blob: fb0737985ef500a2e7aecce0a87703117280b113 [file] [log] [blame]
// Copyright 2022 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/passwords/views_utils.h"
#include <string>
#include <string_view>
#include <vector>
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/grit/generated_resources.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_combobox_model.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/vector_icon_utils.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/editable_combobox/editable_combobox.h"
#include "ui/views/controls/editable_combobox/editable_password_combobox.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/style/typography.h"
namespace {
// Create a vector which contains only the values in |items| and no elements.
std::vector<std::u16string> ToValues(
const password_manager::AlternativeElementVector& items) {
std::vector<std::u16string> passwords;
passwords.reserve(items.size());
for (const auto& item : items) {
passwords.push_back(item.value);
}
return passwords;
}
std::unique_ptr<views::View> CreateRow() {
auto row = std::make_unique<views::View>();
views::FlexLayout* row_layout =
row->SetLayoutManager(std::make_unique<views::FlexLayout>());
row_layout->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetIgnoreDefaultMainAxisMargins(true)
.SetCollapseMargins(true)
.SetDefault(
views::kMarginsKey,
gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
return row;
}
} // namespace
std::unique_ptr<views::StyledLabel> CreateGooglePasswordManagerLabel(
int text_message_id,
int link_message_id,
const std::u16string& email,
base::RepeatingClosure open_link_closure,
int context,
int style) {
const std::u16string link = l10n_util::GetStringUTF16(link_message_id);
std::vector<size_t> offsets;
std::u16string text =
l10n_util::GetStringFUTF16(text_message_id, link, email, &offsets);
auto label = std::make_unique<views::StyledLabel>();
label->SetText(text);
label->SetTextContext(context);
label->SetDefaultTextStyle(style);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->AddStyleRange(
gfx::Range(offsets.at(0), offsets.at(0) + link.length()),
views::StyledLabel::RangeStyleInfo::CreateForLink(open_link_closure));
return label;
}
std::unique_ptr<views::Label> CreateGooglePasswordManagerLabel(
int text_message_id,
int password_manager_message_id,
const std::u16string& email,
int context) {
const std::u16string password_manager =
l10n_util::GetStringUTF16(password_manager_message_id);
std::u16string text =
l10n_util::GetStringFUTF16(text_message_id, password_manager, email);
auto label = std::make_unique<views::Label>(text, context,
views::style::STYLE_SECONDARY);
label->SetMultiLine(true);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
return label;
}
std::unique_ptr<views::StyledLabel> CreateGooglePasswordManagerLabel(
int text_message_id,
int link_message_id,
base::RepeatingClosure open_link_closure,
int context,
int style) {
const std::u16string link = l10n_util::GetStringUTF16(link_message_id);
size_t link_offset;
std::u16string text =
l10n_util::GetStringFUTF16(text_message_id, link, &link_offset);
auto label = std::make_unique<views::StyledLabel>();
label->SetText(text);
label->SetTextContext(context);
label->SetDefaultTextStyle(style);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->AddStyleRange(
gfx::Range(link_offset, link_offset + link.length()),
views::StyledLabel::RangeStyleInfo::CreateForLink(open_link_closure));
return label;
}
std::unique_ptr<views::Label> CreateUsernameLabel(
const password_manager::PasswordForm& form) {
auto label = std::make_unique<views::Label>(
GetDisplayUsername(form), views::style::CONTEXT_DIALOG_BODY_TEXT,
views::style::STYLE_SECONDARY);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
return label;
}
std::unique_ptr<views::Label> CreatePasswordLabel(
const password_manager::PasswordForm& form) {
std::unique_ptr<views::Label> label = std::make_unique<views::Label>(
GetDisplayPassword(form), views::style::CONTEXT_DIALOG_BODY_TEXT);
if (!form.IsFederatedCredential()) {
label->SetTextStyle(views::style::STYLE_SECONDARY_MONOSPACED);
label->SetObscured(true);
label->SetElideBehavior(gfx::TRUNCATE);
} else {
label->SetTextStyle(views::style::STYLE_SECONDARY);
label->SetElideBehavior(gfx::ELIDE_HEAD);
}
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
return label;
}
int ComboboxIconSize() {
// Use the line height of the body small text. This allows the icons to adapt
// if the user changes the font size.
return views::TypographyProvider::Get().GetLineHeight(
views::style::CONTEXT_MENU, views::style::STYLE_PRIMARY);
}
// Builds a credential row, adds the given elements to the layout.
void BuildCredentialRows(views::View* parent_view,
std::unique_ptr<views::View> username_field,
std::unique_ptr<views::View> password_field) {
std::unique_ptr<views::Label> username_label(new views::Label(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL),
views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY));
username_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
std::unique_ptr<views::Label> password_label(new views::Label(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL),
views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY));
password_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
int labels_width = std::max({username_label->GetPreferredSize().width(),
password_label->GetPreferredSize().width()});
int fields_height = std::max({username_field->GetPreferredSize().height(),
password_field->GetPreferredSize().height()});
username_label->SetPreferredSize(gfx::Size(labels_width, fields_height));
password_label->SetPreferredSize(gfx::Size(labels_width, fields_height));
// Username row.
std::unique_ptr<views::View> username_row = CreateRow();
username_row->AddChildView(std::move(username_label));
username_field->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded));
username_row->AddChildView(std::move(username_field));
parent_view->AddChildView(std::move(username_row));
// Password row.
std::unique_ptr<views::View> password_row = CreateRow();
password_row->AddChildView(std::move(password_label));
password_field->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kUnbounded));
password_row->AddChildView(std::move(password_field));
parent_view->AddChildView(std::move(password_row));
}
// Creates an EditableCombobox from |PasswordForm.all_alternative_usernames| or
// even just |PasswordForm.username_value|.
std::unique_ptr<views::EditableCombobox> CreateUsernameEditableCombobox(
const password_manager::PasswordForm& form) {
std::vector<std::u16string> usernames = {form.username_value};
for (const password_manager::AlternativeElement& other_possible_username :
form.all_alternative_usernames) {
if (other_possible_username.value != form.username_value) {
usernames.push_back(other_possible_username.value);
}
}
std::erase_if(usernames, [](const std::u16string& username) {
return username.empty();
});
const bool kDisplayArrow = usernames.size() > 1;
auto combobox = std::make_unique<views::EditableCombobox>(
std::make_unique<ui::SimpleComboboxModel>(
std::vector<ui::SimpleComboboxModel::Item>(usernames.begin(),
usernames.end())),
/*filter_on_edit=*/false, /*show_on_empty=*/true,
views::style::CONTEXT_BUTTON, views::style::STYLE_PRIMARY, kDisplayArrow);
combobox->SetText(form.username_value);
combobox->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL));
// In case of long username, ensure that the beginning of value is visible.
combobox->SelectRange(gfx::Range(0));
return combobox;
}
// Creates an EditablePasswordCombobox from
// `PasswordForm.all_alternative_passwords` or even just
// `PasswordForm.password_value`.
std::unique_ptr<views::EditablePasswordCombobox> CreateEditablePasswordCombobox(
const password_manager::PasswordForm& form,
views::Button::PressedCallback reveal_password_callback) {
DCHECK(!form.IsFederatedCredential());
std::vector<std::u16string> passwords =
form.all_alternative_passwords.empty()
? std::vector<std::u16string>(/*n=*/1, form.password_value)
: ToValues(form.all_alternative_passwords);
std::erase_if(passwords, [](const std::u16string& password) {
return password.empty();
});
const bool kDisplayArrow = passwords.size() > 1;
auto combobox = std::make_unique<views::EditablePasswordCombobox>(
std::make_unique<ui::SimpleComboboxModel>(
std::vector<ui::SimpleComboboxModel::Item>(passwords.begin(),
passwords.end())),
views::style::CONTEXT_BUTTON, views::style::STYLE_PRIMARY_MONOSPACED,
kDisplayArrow, std::move(reveal_password_callback));
combobox->SetText(form.password_value);
combobox->SetPasswordIconTooltips(
l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_SHOW_PASSWORD),
l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_HIDE_PASSWORD));
combobox->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL));
return combobox;
}
std::unique_ptr<views::View> CreateTitleView(const std::u16string& title) {
const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
auto header = std::make_unique<views::BoxLayoutView>();
// Set the space between the icon and title similar to the default
// BubbleFrameView layout.
header->SetBetweenChildSpacing(
layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).left());
header->AddChildView(
std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
GooglePasswordManagerVectorIcon(), ui::kColorIcon,
layout_provider->GetDistanceMetric(
views::DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE))));
views::Label* title_label = header->AddChildView(
views::BubbleFrameView::CreateDefaultTitleLabel(title));
const int close_button_width =
layout_provider->GetDistanceMetric(
views::DISTANCE_RELATED_BUTTON_HORIZONTAL) +
gfx::GetDefaultSizeOfVectorIcon(vector_icons::kCloseRoundedIcon) +
layout_provider->GetDistanceMetric(views::DISTANCE_CLOSE_BUTTON_MARGIN);
const int title_width =
layout_provider->GetDistanceMetric(
views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
layout_provider->GetInsetsMetric(views::INSETS_DIALOG).width() -
layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).width() -
close_button_width;
title_label->SetMaximumWidth(title_width);
return header;
}
void EmphasizeTokens(views::Label* label,
views::style::TextStyle emphasize_style,
const std::vector<std::u16string>& tokens) {
const std::u16string_view text = label->GetText();
for (const std::u16string& token : tokens) {
size_t start = text.find(token, 0);
while (start != std::u16string::npos) {
const size_t end = start + token.size();
label->SetTextStyleRange(emphasize_style, gfx::Range(start, end));
start = text.find(token, end + 1);
}
}
}