1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
7 #include "base/strings/string_split.h"
8 #include "base/strings/string_util.h"
9 #include "chrome/browser/password_manager/password_store_factory.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_finder.h"
12 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
13 #include "components/password_manager/core/browser/password_store.h"
14 #include "components/password_manager/core/common/password_manager_ui.h"
15 #include "grit/generated_resources.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
19 using autofill::PasswordFormMap
;
20 using content::WebContents
;
21 namespace metrics_util
= password_manager::metrics_util
;
25 enum FieldType
{ USERNAME_FIELD
, PASSWORD_FIELD
};
27 const int kUsernameFieldSize
= 30;
28 const int kPasswordFieldSize
= 22;
30 // Returns the width of |type| field.
31 int GetFieldWidth(FieldType type
) {
32 return ui::ResourceBundle::GetSharedInstance()
33 .GetFontList(ui::ResourceBundle::SmallFont
)
34 .GetExpectedTextWidth(type
== USERNAME_FIELD
? kUsernameFieldSize
35 : kPasswordFieldSize
);
38 void SetupLinkifiedText(const base::string16
& string_with_separator
,
40 gfx::Range
* link_range
) {
41 std::vector
<base::string16
> pieces
;
42 base::SplitStringDontTrim(string_with_separator
,
45 DCHECK_EQ(3u, pieces
.size());
46 *link_range
= gfx::Range(pieces
[0].size(),
47 pieces
[0].size() + pieces
[1].size());
48 *text
= JoinString(pieces
, base::string16());
53 ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
54 content::WebContents
* web_contents
)
55 : content::WebContentsObserver(web_contents
),
57 metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
58 dismissal_reason_(metrics_util::NOT_DISPLAYED
) {
59 ManagePasswordsUIController
* controller
=
60 ManagePasswordsUIController::FromWebContents(web_contents
);
62 // TODO(mkwst): Reverse this logic. The controller should populate the model
63 // directly rather than the model pulling from the controller. Perhaps like
64 // `controller->PopulateModel(this)`.
65 state_
= controller
->state();
66 if (password_manager::ui::IsPendingState(state_
))
67 pending_credentials_
= controller
->PendingCredentials();
68 best_matches_
= controller
->best_matches();
70 if (password_manager::ui::IsPendingState(state_
)) {
71 title_
= l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD
);
72 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
73 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED_TITLE
);
74 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
76 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
78 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE
);
82 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
),
83 &save_confirmation_text_
,
84 &save_confirmation_link_range_
);
87 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
90 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
92 void ManagePasswordsBubbleModel::OnBubbleShown(
93 ManagePasswordsBubble::DisplayReason reason
) {
94 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
95 if (password_manager::ui::IsPendingState(state_
)) {
96 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
97 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
98 display_disposition_
= metrics_util::MANUAL_BLACKLISTED
;
100 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
103 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
104 display_disposition_
=
105 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
107 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
110 metrics_util::LogUIDisplayDisposition(display_disposition_
);
112 // Default to a dismissal reason of "no interaction". If the user interacts
113 // with the button in such a way that it closes, we'll reset this value
115 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
118 void ManagePasswordsBubbleModel::OnBubbleHidden() {
119 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
122 metrics_util::LogUIDismissalReason(dismissal_reason_
);
125 void ManagePasswordsBubbleModel::OnNopeClicked() {
126 dismissal_reason_
= metrics_util::CLICKED_NOPE
;
127 state_
= password_manager::ui::PENDING_PASSWORD_STATE
;
130 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
131 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
132 ManagePasswordsUIController
* manage_passwords_ui_controller
=
133 ManagePasswordsUIController::FromWebContents(web_contents());
134 manage_passwords_ui_controller
->NeverSavePassword();
135 state_
= password_manager::ui::BLACKLIST_STATE
;
138 void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
139 dismissal_reason_
= metrics_util::CLICKED_UNBLACKLIST
;
140 ManagePasswordsUIController
* manage_passwords_ui_controller
=
141 ManagePasswordsUIController::FromWebContents(web_contents());
142 manage_passwords_ui_controller
->UnblacklistSite();
143 state_
= password_manager::ui::MANAGE_STATE
;
146 void ManagePasswordsBubbleModel::OnSaveClicked() {
147 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
148 ManagePasswordsUIController
* manage_passwords_ui_controller
=
149 ManagePasswordsUIController::FromWebContents(web_contents());
150 manage_passwords_ui_controller
->SavePassword();
151 state_
= password_manager::ui::MANAGE_STATE
;
154 void ManagePasswordsBubbleModel::OnDoneClicked() {
155 dismissal_reason_
= metrics_util::CLICKED_DONE
;
158 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
159 // User intent is pretty similar in both cases.
160 void ManagePasswordsBubbleModel::OnOKClicked() {
161 dismissal_reason_
= metrics_util::CLICKED_OK
;
164 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
165 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
166 ManagePasswordsUIController::FromWebContents(web_contents())
167 ->NavigateToPasswordManagerSettingsPage();
170 // TODO(gcasto): Is it worth having a new dismissal reason to distinguish
171 // the two management cases? User intention is pretty similar between the two,
172 // but the context in which they are shown is pretty different since one is
173 // from an explict action and the other isn't.
174 void ManagePasswordsBubbleModel::OnRemoteManageLinkClicked() {
175 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
176 ManagePasswordsUIController::FromWebContents(web_contents())
177 ->NavigateToAccountCentralManagementPage();
180 void ManagePasswordsBubbleModel::OnPasswordAction(
181 const autofill::PasswordForm
& password_form
,
182 PasswordAction action
) {
186 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
187 password_manager::PasswordStore
* password_store
=
188 PasswordStoreFactory::GetForProfile(profile
, Profile::EXPLICIT_ACCESS
)
190 DCHECK(password_store
);
191 if (action
== REMOVE_PASSWORD
)
192 password_store
->RemoveLogin(password_form
);
194 password_store
->AddLogin(password_form
);
198 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
199 return GetFieldWidth(USERNAME_FIELD
);
203 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
204 return GetFieldWidth(PASSWORD_FIELD
);