blob: 2c4d6fb5abd36079e746de130995c6cd2f2482cb [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// 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/manage_passwords_bubble_view.h"
#include "base/macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/passwords/password_dialog_prompts.h"
#include "chrome/browser/ui/views/harmony/chrome_layout_provider.h"
#include "chrome/browser/ui/views/harmony/chrome_typography.h"
#include "chrome/browser/ui/views/passwords/credentials_item_view.h"
#include "chrome/browser/ui/views/passwords/credentials_selection_view.h"
#include "chrome/browser/ui/views/passwords/manage_password_items_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/storage_partition.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/combobox_model_observer.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_features.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/text_constants.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/blue_button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/link_listener.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/widget/widget.h"
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
#include "chrome/browser/ui/views/frame/browser_view.h"
#endif
#if defined(OS_WIN)
#include "chrome/browser/ui/views/desktop_ios_promotion/desktop_ios_promotion_bubble_view.h"
#endif
int ManagePasswordsBubbleView::auto_signin_toast_timeout_ = 3;
// Helpers --------------------------------------------------------------------
namespace {
constexpr base::char16 kBulletChar = gfx::RenderText::kPasswordReplacementChar;
enum ColumnSetType {
// | | (FILL, FILL) | |
// Used for the bubble's header, the credentials list, and for simple
// messages like "No passwords".
SINGLE_VIEW_COLUMN_SET,
// | | (LEADING, FILL) | | (FILL, FILL) | |
// Used for the username/password line of the bubble, for the pending view.
DOUBLE_VIEW_COLUMN_SET_USERNAME,
DOUBLE_VIEW_COLUMN_SET_PASSWORD,
// | | (LEADING, FILL) | | (FILL, FILL) | | (TRAILING, FILL) | |
// Used for the password line of the bubble, for the pending view.
// Views are label, password and the eye icon.
TRIPLE_VIEW_COLUMN_SET,
// | | (TRAILING, CENTER) | | (TRAILING, CENTER) | |
// Used for buttons at the bottom of the bubble which should nest at the
// bottom-right corner.
DOUBLE_BUTTON_COLUMN_SET,
// | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
// Used for buttons at the bottom of the bubble which should occupy
// the corners.
LINK_BUTTON_COLUMN_SET,
// | | (TRAILING, CENTER) | |
// Used when there is only one button which should next at the bottom-right
// corner.
SINGLE_BUTTON_COLUMN_SET,
// | | (LEADING, CENTER) | | (TRAILING, CENTER) | | (TRAILING, CENTER) | |
// Used when there are three buttons.
TRIPLE_BUTTON_COLUMN_SET,
};
enum TextRowType { ROW_SINGLE, ROW_MULTILINE };
// A combobox model for password dropdown that allows to mask/unmask values in
// the combobox.
class PasswordDropdownModel : public ui::ComboboxModel {
public:
explicit PasswordDropdownModel(const std::vector<base::string16>& items)
: masked_(true), passwords_(items) {}
~PasswordDropdownModel() override {}
void SetMasked(bool masked) {
if (masked_ == masked)
return;
masked_ = masked;
for (auto& observer : observers_)
observer.OnComboboxModelChanged(this);
}
// ui::ComboboxModel:
int GetItemCount() const override { return passwords_.size(); }
base::string16 GetItemAt(int index) override {
return masked_ ? base::string16(passwords_[index].length(), kBulletChar)
: passwords_[index];
}
void AddObserver(ui::ComboboxModelObserver* observer) override {
observers_.AddObserver(observer);
}
void RemoveObserver(ui::ComboboxModelObserver* observer) override {
observers_.RemoveObserver(observer);
}
private:
bool masked_;
const std::vector<base::string16> passwords_;
// To be called when |masked_| was changed;
base::ObserverList<ui::ComboboxModelObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(PasswordDropdownModel);
};
// Construct an appropriate ColumnSet for the given |type|, and add it
// to |layout|.
void BuildColumnSet(views::GridLayout* layout, ColumnSetType type) {
views::ColumnSet* column_set = layout->AddColumnSet(type);
int full_width = ManagePasswordsBubbleView::kDesiredBubbleWidth;
const int button_divider = ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_BUTTON_HORIZONTAL);
const int column_divider = ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
switch (type) {
case SINGLE_VIEW_COLUMN_SET:
column_set->AddColumn(views::GridLayout::FILL,
views::GridLayout::FILL,
0,
views::GridLayout::FIXED,
full_width,
0);
break;
case DOUBLE_VIEW_COLUMN_SET_USERNAME:
case DOUBLE_VIEW_COLUMN_SET_PASSWORD:
column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
0, views::GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(0, column_divider);
column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
views::GridLayout::USE_PREF, 0, 0);
break;
case TRIPLE_VIEW_COLUMN_SET:
column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
0, views::GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(0, column_divider);
column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
views::GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(0, column_divider);
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::FILL, 0,
views::GridLayout::USE_PREF, 0, 0);
break;
case DOUBLE_BUTTON_COLUMN_SET:
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
1,
views::GridLayout::USE_PREF,
0,
0);
column_set->AddPaddingColumn(0, button_divider);
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
0,
views::GridLayout::USE_PREF,
0,
0);
break;
case LINK_BUTTON_COLUMN_SET:
column_set->AddColumn(views::GridLayout::LEADING,
views::GridLayout::CENTER,
1,
views::GridLayout::USE_PREF,
0,
0);
column_set->AddPaddingColumn(0, button_divider);
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
0,
views::GridLayout::USE_PREF,
0,
0);
break;
case SINGLE_BUTTON_COLUMN_SET:
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
1,
views::GridLayout::USE_PREF,
0,
0);
break;
case TRIPLE_BUTTON_COLUMN_SET:
column_set->AddColumn(views::GridLayout::LEADING,
views::GridLayout::CENTER,
1,
views::GridLayout::USE_PREF,
0,
0);
column_set->AddPaddingColumn(0, button_divider);
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
0,
views::GridLayout::USE_PREF,
0,
0);
column_set->AddPaddingColumn(0, button_divider);
column_set->AddColumn(views::GridLayout::TRAILING,
views::GridLayout::CENTER,
0,
views::GridLayout::USE_PREF,
0,
0);
break;
}
}
views::StyledLabel::RangeStyleInfo GetLinkStyle() {
auto result = views::StyledLabel::RangeStyleInfo::CreateForLink();
result.disable_line_wrapping = false;
return result;
}
std::unique_ptr<views::ToggleImageButton> CreatePasswordViewButton(
views::ButtonListener* listener) {
std::unique_ptr<views::ToggleImageButton> button(
new views::ToggleImageButton(listener));
button->SetFocusForPlatform();
button->set_request_focus_on_press(true);
button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_SHOW_PASSWORD));
button->SetToggledTooltipText(
l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_HIDE_PASSWORD));
button->SetImage(views::ImageButton::STATE_NORMAL,
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_SHOW_PASSWORD_HOVER));
button->SetToggledImage(
views::ImageButton::STATE_NORMAL,
ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_HIDE_PASSWORD_HOVER));
button->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
views::ImageButton::ALIGN_MIDDLE);
return button;
}
// Creates a dropdown from the other possible passwords.
std::unique_ptr<views::Combobox> CreatePasswordDropdownView(
const autofill::PasswordForm& form) {
DCHECK(!form.all_possible_passwords.empty());
std::unique_ptr<views::Combobox> combobox = std::make_unique<views::Combobox>(
std::make_unique<PasswordDropdownModel>(form.all_possible_passwords));
size_t index = std::distance(
form.all_possible_passwords.begin(),
find(form.all_possible_passwords.begin(),
form.all_possible_passwords.end(), form.password_value));
// Unlikely, but if we don't find the password in possible passwords,
// we will set the default to first element.
if (index == form.all_possible_passwords.size()) {
combobox->SetSelectedIndex(0);
} else {
combobox->SetSelectedIndex(index);
}
combobox->SetAccessibleName(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL));
return combobox;
}
// Builds a credential row, adds the given elements to the layout.
// |password_view_button| is an optional field. If it is a nullptr, a
// DOUBLE_VIEW_COLUMN_SET_PASSWORD will be used for password row instead of
// TRIPLE_VIEW_COLUMN_SET.
void BuildCredentialRows(views::GridLayout* layout,
views::View* username_field,
views::View* password_field,
views::ToggleImageButton* password_view_button,
bool show_password_label) {
// Username row.
BuildColumnSet(layout, DOUBLE_VIEW_COLUMN_SET_USERNAME);
layout->StartRow(0, DOUBLE_VIEW_COLUMN_SET_USERNAME);
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_RIGHT);
std::unique_ptr<views::Label> password_label(new views::Label(
show_password_label
? l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL)
: base::string16(),
views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY));
password_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_RIGHT);
int labels_width = std::max(username_label->GetPreferredSize().width(),
password_label->GetPreferredSize().width());
layout->AddView(username_label.release(), 1, 1, views::GridLayout::LEADING,
views::GridLayout::FILL, labels_width, 0);
layout->AddView(username_field, 1, 1, views::GridLayout::FILL,
views::GridLayout::FILL, 0, 0);
layout->AddPaddingRow(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_CONTROL_LIST_VERTICAL));
// Password row.
ColumnSetType type = password_view_button ? TRIPLE_VIEW_COLUMN_SET
: DOUBLE_VIEW_COLUMN_SET_PASSWORD;
BuildColumnSet(layout, type);
layout->StartRow(0, type);
layout->AddView(password_label.release(), 1, 1, views::GridLayout::LEADING,
views::GridLayout::FILL, labels_width, 0);
layout->AddView(password_field, 1, 1, views::GridLayout::FILL,
views::GridLayout::FILL, 0, 0);
// The eye icon is also added to the layout if it was passed.
if (password_view_button) {
layout->AddView(password_view_button);
}
}
} // namespace
// ManagePasswordsBubbleView::AutoSigninView ----------------------------------
// A view containing just one credential that was used for for automatic signing
// in.
class ManagePasswordsBubbleView::AutoSigninView
: public views::View,
public views::ButtonListener,
public views::WidgetObserver {
public:
explicit AutoSigninView(ManagePasswordsBubbleView* parent);
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::WidgetObserver:
// Tracks the state of the browser window.
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
void OnWidgetClosing(views::Widget* widget) override;
void OnTimer();
static base::TimeDelta GetTimeout() {
return base::TimeDelta::FromSeconds(
ManagePasswordsBubbleView::auto_signin_toast_timeout_);
}
base::OneShotTimer timer_;
ManagePasswordsBubbleView* parent_;
ScopedObserver<views::Widget, views::WidgetObserver> observed_browser_;
DISALLOW_COPY_AND_ASSIGN(AutoSigninView);
};
ManagePasswordsBubbleView::AutoSigninView::AutoSigninView(
ManagePasswordsBubbleView* parent)
: parent_(parent),
observed_browser_(this) {
SetLayoutManager(new views::FillLayout);
const autofill::PasswordForm& form = parent_->model()->pending_password();
CredentialsItemView* credential;
base::string16 upper_text, lower_text = form.username_value;
if (ChromeLayoutProvider::Get()->IsHarmonyMode()) {
upper_text =
l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_AUTO_SIGNIN_TITLE_MD);
} else {
lower_text = l10n_util::GetStringFUTF16(
IDS_MANAGE_PASSWORDS_AUTO_SIGNIN_TITLE, lower_text);
}
credential = new CredentialsItemView(
this, upper_text, lower_text, kButtonHoverColor, &form,
content::BrowserContext::GetDefaultStoragePartition(
parent_->model()->GetProfile())
->GetURLLoaderFactoryForBrowserProcess());
credential->SetEnabled(false);
AddChildView(credential);
// Setup the observer and maybe start the timer.
Browser* browser =
chrome::FindBrowserWithWebContents(parent_->GetWebContents());
DCHECK(browser);
// Sign-in dialogs opened for inactive browser windows do not auto-close on
// MacOS. This matches existing Cocoa bubble behavior.
// TODO(varkha): Remove the limitation as part of http://crbug/671916 .
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
observed_browser_.Add(browser_view->GetWidget());
#endif
if (browser->window()->IsActive())
timer_.Start(FROM_HERE, GetTimeout(), this, &AutoSigninView::OnTimer);
}
void ManagePasswordsBubbleView::AutoSigninView::ButtonPressed(
views::Button* sender, const ui::Event& event) {
NOTREACHED();
}
void ManagePasswordsBubbleView::AutoSigninView::OnWidgetActivationChanged(
views::Widget* widget, bool active) {
if (active && !timer_.IsRunning())
timer_.Start(FROM_HERE, GetTimeout(), this, &AutoSigninView::OnTimer);
}
void ManagePasswordsBubbleView::AutoSigninView::OnWidgetClosing(
views::Widget* widget) {
observed_browser_.RemoveAll();
}
void ManagePasswordsBubbleView::AutoSigninView::OnTimer() {
parent_->model()->OnAutoSignInToastTimeout();
parent_->CloseBubble();
}
// ManagePasswordsBubbleView::PendingView -------------------------------------
// A view offering the user the ability to save credentials. Contains a
// username and password field, along with a "Save Passwords" button and a
// "Never" button.
class ManagePasswordsBubbleView::PendingView : public views::View,
public views::ButtonListener {
public:
explicit PendingView(ManagePasswordsBubbleView* parent);
~PendingView() override;
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// View:
gfx::Size CalculatePreferredSize() const override;
void CreateAndSetLayout(bool show_password_label);
void CreatePasswordField();
void TogglePasswordVisibility();
void UpdateUsernameAndPasswordInModel();
ManagePasswordsBubbleView* parent_;
views::Button* save_button_;
views::Button* never_button_;
views::View* username_field_;
views::ToggleImageButton* password_view_button_;
// The view for the password value. Only one of |password_dropdown_| and
// |password_label_| should be available.
views::Combobox* password_dropdown_;
views::Label* password_label_;
bool password_visible_;
DISALLOW_COPY_AND_ASSIGN(PendingView);
};
ManagePasswordsBubbleView::PendingView::PendingView(
ManagePasswordsBubbleView* parent)
: parent_(parent),
save_button_(nullptr),
never_button_(nullptr),
username_field_(nullptr),
password_view_button_(nullptr),
password_dropdown_(nullptr),
password_label_(nullptr),
password_visible_(false) {
// Create credentials row.
const autofill::PasswordForm& password_form =
parent_->model()->pending_password();
const bool is_password_credential = password_form.federation_origin.unique();
if (base::FeatureList::IsEnabled(
password_manager::features::kEnableUsernameCorrection) &&
parent_->model()->enable_editing()) {
username_field_ = CreateUsernameEditable(password_form).release();
} else {
username_field_ = CreateUsernameLabel(password_form).release();
}
CreatePasswordField();
if (base::FeatureList::IsEnabled(
password_manager::features::kEnablePasswordSelection) &&
!parent_->model()->hide_eye_icon() && is_password_credential) {
password_view_button_ = CreatePasswordViewButton(this).release();
}
// Create buttons.
save_button_ = views::MdTextButton::CreateSecondaryUiBlueButton(
this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON));
never_button_ = views::MdTextButton::CreateSecondaryUiButton(
this,
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_BUBBLE_BLACKLIST_BUTTON));
CreateAndSetLayout(is_password_credential);
if (base::FeatureList::IsEnabled(
password_manager::features::kEnableUsernameCorrection) &&
parent_->model()->enable_editing() &&
parent_->model()->pending_password().username_value.empty()) {
parent_->set_initially_focused_view(username_field_);
} else {
parent_->set_initially_focused_view(save_button_);
}
}
ManagePasswordsBubbleView::PendingView::~PendingView() = default;
void ManagePasswordsBubbleView::PendingView::ButtonPressed(
views::Button* sender,
const ui::Event& event) {
if (sender == save_button_) {
UpdateUsernameAndPasswordInModel();
parent_->model()->OnSaveClicked();
if (parent_->model()->ReplaceToShowPromotionIfNeeded()) {
parent_->Refresh();
return;
}
} else if (sender == never_button_) {
parent_->model()->OnNeverForThisSiteClicked();
} else if (sender == password_view_button_) {
TogglePasswordVisibility();
return;
} else {
NOTREACHED();
}
parent_->CloseBubble();
}
gfx::Size ManagePasswordsBubbleView::PendingView::CalculatePreferredSize()
const {
return gfx::Size(kDesiredBubbleWidth,
GetLayoutManager()->GetPreferredHeightForWidth(
this, kDesiredBubbleWidth));
}
void ManagePasswordsBubbleView::PendingView::CreateAndSetLayout(
bool show_password_label) {
views::GridLayout* layout = views::GridLayout::CreateAndInstall(this);
layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
views::View* password_field =
password_dropdown_ ? static_cast<views::View*>(password_dropdown_)
: static_cast<views::View*>(password_label_);
BuildCredentialRows(layout, username_field_, password_field,
password_view_button_, show_password_label);
layout->AddPaddingRow(
0, ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL));
// Button row.
BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
layout->StartRow(0, DOUBLE_BUTTON_COLUMN_SET);
layout->AddView(save_button_);
layout->AddView(never_button_);
}
void ManagePasswordsBubbleView::PendingView::CreatePasswordField() {
const bool enable_password_selection = base::FeatureList::IsEnabled(
password_manager::features::kEnablePasswordSelection);
const autofill::PasswordForm& password_form =
parent_->model()->pending_password();
if (enable_password_selection &&
password_form.all_possible_passwords.size() > 1 &&
parent_->model()->enable_editing()) {
password_dropdown_ = CreatePasswordDropdownView(password_form).release();
} else {
password_label_ =
CreatePasswordLabel(password_form, password_visible_).release();
}
}
void ManagePasswordsBubbleView::PendingView::TogglePasswordVisibility() {
UpdateUsernameAndPasswordInModel();
password_visible_ = !password_visible_;
password_view_button_->SetToggled(password_visible_);
DCHECK(!password_dropdown_ || !password_label_);
if (password_dropdown_) {
static_cast<PasswordDropdownModel*>(password_dropdown_->model())
->SetMasked(!password_visible_);
} else {
password_label_->SetObscured(!password_visible_);
}
}
void ManagePasswordsBubbleView::PendingView::
UpdateUsernameAndPasswordInModel() {
const bool username_editable =
base::FeatureList::IsEnabled(
password_manager::features::kEnableUsernameCorrection) &&
parent_->model()->enable_editing();
const bool password_editable =
base::FeatureList::IsEnabled(
password_manager::features::kEnablePasswordSelection) &&
password_dropdown_ && parent_->model()->enable_editing();
if (!username_editable && !password_editable)
return;
base::string16 new_username =
parent_->model()->pending_password().username_value;
base::string16 new_password =
parent_->model()->pending_password().password_value;
if (username_editable) {
new_username = static_cast<views::Textfield*>(username_field_)->text();
}
if (password_editable) {
new_password =
parent_->model()->pending_password().all_possible_passwords.at(
password_dropdown_->selected_index());
}
parent_->model()->OnCredentialEdited(new_username, new_password);
}
// ManagePasswordsBubbleView::SaveConfirmationView ----------------------------
// A view confirming to the user that a password was saved and offering a link
// to the Google account manager.
class ManagePasswordsBubbleView::SaveConfirmationView
: public views::View,
public views::ButtonListener,
public views::StyledLabelListener {
public:
explicit SaveConfirmationView(ManagePasswordsBubbleView* parent);
~SaveConfirmationView() override;
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::StyledLabelListener implementation
void StyledLabelLinkClicked(views::StyledLabel* label,
const gfx::Range& range,
int event_flags) override;
ManagePasswordsBubbleView* parent_;
views::Button* ok_button_;
DISALLOW_COPY_AND_ASSIGN(SaveConfirmationView);
};
ManagePasswordsBubbleView::SaveConfirmationView::SaveConfirmationView(
ManagePasswordsBubbleView* parent)
: parent_(parent) {
views::GridLayout* layout = views::GridLayout::CreateAndInstall(this);
layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
views::StyledLabel* confirmation =
new views::StyledLabel(parent_->model()->save_confirmation_text(), this);
confirmation->SetTextContext(CONTEXT_DEPRECATED_SMALL);
confirmation->AddStyleRange(parent_->model()->save_confirmation_link_range(),
GetLinkStyle());
BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
layout->AddView(confirmation);
ok_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_OK));
ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
layout->AddPaddingRow(0,
layout_provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_TEXT));
BuildColumnSet(layout, SINGLE_BUTTON_COLUMN_SET);
layout->StartRow(0, SINGLE_BUTTON_COLUMN_SET);
layout->AddView(ok_button_);
parent_->set_initially_focused_view(ok_button_);
}
ManagePasswordsBubbleView::SaveConfirmationView::~SaveConfirmationView() {
}
void ManagePasswordsBubbleView::SaveConfirmationView::StyledLabelLinkClicked(
views::StyledLabel* label,
const gfx::Range& range,
int event_flags) {
DCHECK_EQ(range, parent_->model()->save_confirmation_link_range());
parent_->model()->OnNavigateToPasswordManagerAccountDashboardLinkClicked();
parent_->CloseBubble();
}
void ManagePasswordsBubbleView::SaveConfirmationView::ButtonPressed(
views::Button* sender, const ui::Event& event) {
DCHECK_EQ(sender, ok_button_);
parent_->model()->OnOKClicked();
parent_->CloseBubble();
}
// ManagePasswordsBubbleView::SignInPromoView ---------------------------------
// A view that offers user to sign in to Chrome.
class ManagePasswordsBubbleView::SignInPromoView
: public views::View,
public views::ButtonListener {
public:
explicit SignInPromoView(ManagePasswordsBubbleView* parent);
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
ManagePasswordsBubbleView* parent_;
views::Button* signin_button_;
views::Button* no_button_;
DISALLOW_COPY_AND_ASSIGN(SignInPromoView);
};
ManagePasswordsBubbleView::SignInPromoView::SignInPromoView(
ManagePasswordsBubbleView* parent)
: parent_(parent) {
views::GridLayout* layout = views::GridLayout::CreateAndInstall(this);
layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
signin_button_ = views::MdTextButton::CreateSecondaryUiBlueButton(
this,
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SIGNIN_PROMO_SIGN_IN));
no_button_ = views::MdTextButton::CreateSecondaryUiButton(
this,
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SIGNIN_PROMO_NO_THANKS));
// Button row.
BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
layout->StartRow(0, DOUBLE_BUTTON_COLUMN_SET);
layout->AddView(signin_button_);
layout->AddView(no_button_);
parent_->set_initially_focused_view(signin_button_);
base::RecordAction(
base::UserMetricsAction("Signin_Impression_FromPasswordBubble"));
}
void ManagePasswordsBubbleView::SignInPromoView::ButtonPressed(
views::Button* sender,
const ui::Event& event) {
if (sender == signin_button_)
parent_->model()->OnSignInToChromeClicked();
else if (sender == no_button_)
parent_->model()->OnSkipSignInClicked();
else
NOTREACHED();
parent_->CloseBubble();
}
// ManagePasswordsBubbleView::UpdatePendingView -------------------------------
// A view offering the user the ability to update credentials. Contains a
// single credential row (in case of one credentials) or
// CredentialsSelectionView otherwise, along with a "Update Passwords" button
// and a rejection button.
class ManagePasswordsBubbleView::UpdatePendingView
: public views::View,
public views::ButtonListener {
public:
explicit UpdatePendingView(ManagePasswordsBubbleView* parent);
~UpdatePendingView() override;
private:
// views::ButtonListener:
void ButtonPressed(views::Button* sender, const ui::Event& event) override;
// views::View:
gfx::Size CalculatePreferredSize() const override;
ManagePasswordsBubbleView* parent_;
CredentialsSelectionView* selection_view_;
views::Button* update_button_;
views::Button* nope_button_;
DISALLOW_COPY_AND_ASSIGN(UpdatePendingView);
};
ManagePasswordsBubbleView::UpdatePendingView::UpdatePendingView(
ManagePasswordsBubbleView* parent)
: parent_(parent), selection_view_(nullptr) {
ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
views::GridLayout* layout = views::GridLayout::CreateAndInstall(this);
layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
// Credential row.
if (parent->model()->ShouldShowMultipleAccountUpdateUI()) {
BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
layout->AddView(new CredentialsSelectionView(parent->model()));
} else {
const autofill::PasswordForm& password_form =
parent_->model()->pending_password();
BuildCredentialRows(layout, CreateUsernameLabel(password_form).release(),
CreatePasswordLabel(password_form, false).release(),
nullptr, /* password_view_button */
true /* show_password_label */);
}
layout->AddPaddingRow(
0, layout_provider->GetDistanceMetric(
views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL));
// Button row.
nope_button_ = views::MdTextButton::CreateSecondaryUiButton(
this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_CANCEL_BUTTON));
update_button_ = views::MdTextButton::CreateSecondaryUiBlueButton(
this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UPDATE_BUTTON));
BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
layout->StartRow(0, DOUBLE_BUTTON_COLUMN_SET);
layout->AddView(update_button_);
layout->AddView(nope_button_);
parent_->set_initially_focused_view(update_button_);
}
ManagePasswordsBubbleView::UpdatePendingView::~UpdatePendingView() {}
void ManagePasswordsBubbleView::UpdatePendingView::ButtonPressed(
views::Button* sender,
const ui::Event& event) {
DCHECK(sender == update_button_ || sender == nope_button_);
if (sender == update_button_) {
if (selection_view_) {
// Multi account case.
parent_->model()->OnUpdateClicked(
*selection_view_->GetSelectedCredentials());
} else {
parent_->model()->OnUpdateClicked(parent_->model()->pending_password());
}
} else {
parent_->model()->OnNopeUpdateClicked();
}
parent_->CloseBubble();
}
gfx::Size ManagePasswordsBubbleView::UpdatePendingView::CalculatePreferredSize()
const {
return gfx::Size(kDesiredBubbleWidth,
GetLayoutManager()->GetPreferredHeightForWidth(
this, kDesiredBubbleWidth));
}
// ManagePasswordsBubbleView --------------------------------------------------
ManagePasswordsBubbleView::ManagePasswordsBubbleView(
content::WebContents* web_contents,
views::View* anchor_view,
const gfx::Point& anchor_point,
DisplayReason reason)
: ManagePasswordsBubbleDelegateViewBase(web_contents,
anchor_view,
anchor_point,
reason),
initially_focused_view_(nullptr) {
set_margins(
ChromeLayoutProvider::Get()->GetInsetsMetric(views::INSETS_DIALOG));
chrome::RecordDialogCreation(chrome::DialogIdentifier::MANAGE_PASSWORDS);
}
ManagePasswordsBubbleView::~ManagePasswordsBubbleView() = default;
bool ManagePasswordsBubbleView::ShouldSnapFrameWidth() const {
return ChromeLayoutProvider::Get()->IsHarmonyMode();
}
int ManagePasswordsBubbleView::GetDialogButtons() const {
// TODO(tapted): DialogClientView should manage buttons.
return ui::DIALOG_BUTTON_NONE;
}
views::View* ManagePasswordsBubbleView::GetInitiallyFocusedView() {
return initially_focused_view_;
}
void ManagePasswordsBubbleView::Init() {
SetLayoutManager(new views::FillLayout);
CreateChild();
}
void ManagePasswordsBubbleView::AddedToWidget() {
auto title_view =
base::MakeUnique<views::StyledLabel>(base::string16(), this);
title_view->SetTextContext(views::style::CONTEXT_DIALOG_TITLE);
UpdateTitleText(title_view.get());
GetBubbleFrameView()->SetTitleView(std::move(title_view));
}
void ManagePasswordsBubbleView::UpdateTitleText(
views::StyledLabel* title_view) {
title_view->SetText(GetWindowTitle());
if (!model()->title_brand_link_range().is_empty()) {
title_view->AddStyleRange(model()->title_brand_link_range(),
GetLinkStyle());
}
}
gfx::ImageSkia ManagePasswordsBubbleView::GetWindowIcon() {
#if defined(OS_WIN)
if (model()->state() ==
password_manager::ui::CHROME_DESKTOP_IOS_PROMO_STATE) {
return desktop_ios_promotion::GetPromoImage(
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_TextfieldDefaultColor));
}
#endif
return gfx::ImageSkia();
}
bool ManagePasswordsBubbleView::ShouldShowWindowIcon() const {
return model()->state() ==
password_manager::ui::CHROME_DESKTOP_IOS_PROMO_STATE;
}
bool ManagePasswordsBubbleView::ShouldShowCloseButton() const {
return model()->state() == password_manager::ui::PENDING_PASSWORD_STATE ||
model()->state() == password_manager::ui::CHROME_SIGN_IN_PROMO_STATE ||
model()->state() ==
password_manager::ui::CHROME_DESKTOP_IOS_PROMO_STATE;
}
void ManagePasswordsBubbleView::StyledLabelLinkClicked(
views::StyledLabel* label,
const gfx::Range& range,
int event_flags) {
DCHECK_EQ(model()->title_brand_link_range(), range);
model()->OnBrandLinkClicked();
}
void ManagePasswordsBubbleView::Refresh() {
RemoveAllChildViews(true);
initially_focused_view_ = NULL;
CreateChild();
// Show/hide the close button.
GetWidget()->non_client_view()->ResetWindowControls();
GetWidget()->UpdateWindowIcon();
UpdateTitleText(
static_cast<views::StyledLabel*>(GetBubbleFrameView()->title()));
if (model()->state() ==
password_manager::ui::CHROME_DESKTOP_IOS_PROMO_STATE) {
// Update the height and keep the existing width.
gfx::Rect bubble_bounds = GetWidget()->GetWindowBoundsInScreen();
bubble_bounds.set_height(
GetWidget()->GetRootView()->GetHeightForWidth(bubble_bounds.width()));
GetWidget()->SetBounds(bubble_bounds);
} else {
SizeToContents();
}
}
void ManagePasswordsBubbleView::CreateChild() {
if (model()->state() == password_manager::ui::PENDING_PASSWORD_STATE) {
AddChildView(new PendingView(this));
} else if (model()->state() ==
password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
AddChildView(new UpdatePendingView(this));
} else if (model()->state() == password_manager::ui::CONFIRMATION_STATE) {
AddChildView(new SaveConfirmationView(this));
} else if (model()->state() == password_manager::ui::AUTO_SIGNIN_STATE) {
AddChildView(new AutoSigninView(this));
} else if (model()->state() ==
password_manager::ui::CHROME_SIGN_IN_PROMO_STATE) {
AddChildView(new SignInPromoView(this));
#if defined(OS_WIN)
} else if (model()->state() ==
password_manager::ui::CHROME_DESKTOP_IOS_PROMO_STATE) {
AddChildView(new DesktopIOSPromotionBubbleView(
model()->GetProfile(),
desktop_ios_promotion::PromotionEntryPoint::SAVE_PASSWORD_BUBBLE));
#endif
} else {
// This model state should be handled by separate dialogs.
NOTREACHED();
}
}