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/command_line.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/password_manager/password_store_factory.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/ui/browser.h"
12 #include "chrome/browser/ui/browser_finder.h"
13 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
14 #include "chrome/browser/ui/passwords/password_bubble_experiment.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "components/feedback/feedback_data.h"
17 #include "components/feedback/feedback_util.h"
18 #include "components/password_manager/core/browser/password_store.h"
19 #include "components/password_manager/core/common/credential_manager_types.h"
20 #include "components/password_manager/core/common/password_manager_ui.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/common/content_switches.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
26 using autofill::PasswordFormMap
;
27 using feedback::FeedbackData
;
28 using content::WebContents
;
29 namespace metrics_util
= password_manager::metrics_util
;
33 enum FieldType
{ USERNAME_FIELD
, PASSWORD_FIELD
};
35 const int kUsernameFieldSize
= 30;
36 const int kPasswordFieldSize
= 22;
38 // Returns the width of |type| field.
39 int GetFieldWidth(FieldType type
) {
40 return ui::ResourceBundle::GetSharedInstance()
41 .GetFontList(ui::ResourceBundle::SmallFont
)
42 .GetExpectedTextWidth(type
== USERNAME_FIELD
? kUsernameFieldSize
43 : kPasswordFieldSize
);
46 Profile
* GetProfileFromWebContents(content::WebContents
* web_contents
) {
49 return Profile::FromBrowserContext(web_contents
->GetBrowserContext());
52 void RecordExperimentStatistics(content::WebContents
* web_contents
,
53 metrics_util::UIDismissalReason reason
) {
54 Profile
* profile
= GetProfileFromWebContents(web_contents
);
57 password_bubble_experiment::RecordBubbleClosed(profile
->GetPrefs(), reason
);
60 ScopedVector
<const autofill::PasswordForm
> DeepCopyForms(
61 const std::vector
<const autofill::PasswordForm
*>& forms
) {
62 ScopedVector
<const autofill::PasswordForm
> result
;
63 result
.reserve(forms
.size());
64 std::transform(forms
.begin(), forms
.end(), std::back_inserter(result
),
65 [](const autofill::PasswordForm
* form
) {
66 return new autofill::PasswordForm(*form
);
73 ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
74 content::WebContents
* web_contents
)
75 : content::WebContentsObserver(web_contents
),
76 never_save_passwords_(false),
77 display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
78 dismissal_reason_(metrics_util::NOT_DISPLAYED
) {
79 ManagePasswordsUIController
* controller
=
80 ManagePasswordsUIController::FromWebContents(web_contents
);
82 origin_
= controller
->origin();
83 state_
= controller
->state();
84 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
85 pending_password_
= controller
->PendingPassword();
86 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
87 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
88 // We don't need anything.
89 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
90 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
91 federated_credentials_
= DeepCopyForms(controller
->GetFederatedForms());
92 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
93 pending_password_
= *controller
->GetCurrentForms()[0];
95 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
98 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
99 title_
= PendingStateTitleBasedOnSavePasswordPref();
100 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
101 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED_TITLE
);
102 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
104 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
105 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
106 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CHOOSE_TITLE
);
107 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
108 // There is no title.
110 title_
= IsNewUIActive() ?
111 l10n_util::GetStringFUTF16(IDS_MANAGE_ACCOUNTS_TITLE
,
112 base::UTF8ToUTF16(origin_
.spec())) :
113 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE
);
116 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
117 base::string16 save_confirmation_link
=
118 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_LINK
);
120 save_confirmation_text_
=
121 l10n_util::GetStringFUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
,
122 save_confirmation_link
, &offset
);
123 save_confirmation_link_range_
=
124 gfx::Range(offset
, offset
+ save_confirmation_link
.length());
128 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
131 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
133 void ManagePasswordsBubbleModel::OnBubbleShown(
134 ManagePasswordsBubble::DisplayReason reason
) {
135 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
136 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
137 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
138 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
139 display_disposition_
= metrics_util::MANUAL_BLACKLISTED
;
141 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
144 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
145 display_disposition_
=
146 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
147 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
148 display_disposition_
= metrics_util::AUTOMATIC_CREDENTIAL_REQUEST
;
149 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
150 display_disposition_
= metrics_util::AUTOMATIC_SIGNIN_TOAST
;
152 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
155 metrics_util::LogUIDisplayDisposition(display_disposition_
);
157 // Default to a dismissal reason of "no interaction". If the user interacts
158 // with the button in such a way that it closes, we'll reset this value
160 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
162 ManagePasswordsUIController
* controller
=
163 ManagePasswordsUIController::FromWebContents(web_contents());
164 controller
->OnBubbleShown();
167 void ManagePasswordsBubbleModel::OnBubbleHidden() {
168 ManagePasswordsUIController
* manage_passwords_ui_controller
=
170 ManagePasswordsUIController::FromWebContents(web_contents())
172 if (manage_passwords_ui_controller
)
173 manage_passwords_ui_controller
->OnBubbleHidden();
174 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
177 metrics_util::LogUIDismissalReason(dismissal_reason_
);
178 // Other use cases have been reported in the callbacks like OnSaveClicked().
179 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
&&
180 dismissal_reason_
== metrics_util::NO_DIRECT_INTERACTION
)
181 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
184 void ManagePasswordsBubbleModel::OnNopeClicked() {
185 dismissal_reason_
= metrics_util::CLICKED_NOPE
;
186 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
187 if (state_
!= password_manager::ui::CREDENTIAL_REQUEST_STATE
)
188 state_
= password_manager::ui::PENDING_PASSWORD_STATE
;
191 void ManagePasswordsBubbleModel::OnConfirmationForNeverForThisSite() {
192 never_save_passwords_
= true;
193 title_
= PendingStateTitleBasedOnSavePasswordPref();
196 void ManagePasswordsBubbleModel::OnUndoNeverForThisSite() {
197 never_save_passwords_
= false;
198 title_
= PendingStateTitleBasedOnSavePasswordPref();
201 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
202 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
203 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
204 ManagePasswordsUIController
* manage_passwords_ui_controller
=
205 ManagePasswordsUIController::FromWebContents(web_contents());
206 manage_passwords_ui_controller
->NeverSavePassword();
207 state_
= password_manager::ui::BLACKLIST_STATE
;
210 void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
211 dismissal_reason_
= metrics_util::CLICKED_UNBLACKLIST
;
212 ManagePasswordsUIController
* manage_passwords_ui_controller
=
213 ManagePasswordsUIController::FromWebContents(web_contents());
214 manage_passwords_ui_controller
->UnblacklistSite();
215 state_
= password_manager::ui::MANAGE_STATE
;
218 void ManagePasswordsBubbleModel::OnSaveClicked() {
219 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
220 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
221 ManagePasswordsUIController
* manage_passwords_ui_controller
=
222 ManagePasswordsUIController::FromWebContents(web_contents());
223 manage_passwords_ui_controller
->SavePassword();
224 state_
= password_manager::ui::MANAGE_STATE
;
227 void ManagePasswordsBubbleModel::OnDoneClicked() {
228 dismissal_reason_
= metrics_util::CLICKED_DONE
;
231 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
232 // User intent is pretty similar in both cases.
233 void ManagePasswordsBubbleModel::OnOKClicked() {
234 dismissal_reason_
= metrics_util::CLICKED_OK
;
237 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
238 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
239 ManagePasswordsUIController::FromWebContents(web_contents())
240 ->NavigateToPasswordManagerSettingsPage();
243 void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() {
244 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT
;
247 void ManagePasswordsBubbleModel::OnAutoSignInClicked() {
248 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_CLICKED
;
249 ManagePasswordsUIController
* manage_passwords_ui_controller
=
250 ManagePasswordsUIController::FromWebContents(web_contents());
251 manage_passwords_ui_controller
->ManageAccounts();
252 state_
= password_manager::ui::MANAGE_STATE
;
255 void ManagePasswordsBubbleModel::OnPasswordAction(
256 const autofill::PasswordForm
& password_form
,
257 PasswordAction action
) {
261 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
262 password_manager::PasswordStore
* password_store
=
263 PasswordStoreFactory::GetForProfile(
264 profile
, ServiceAccessType::EXPLICIT_ACCESS
).get();
265 DCHECK(password_store
);
266 if (action
== REMOVE_PASSWORD
)
267 password_store
->RemoveLogin(password_form
);
269 password_store
->AddLogin(password_form
);
272 void ManagePasswordsBubbleModel::OnChooseCredentials(
273 const autofill::PasswordForm
& password_form
,
274 password_manager::CredentialType credential_type
) {
275 dismissal_reason_
= metrics_util::CLICKED_CREDENTIAL
;
276 ManagePasswordsUIController
* manage_passwords_ui_controller
=
277 ManagePasswordsUIController::FromWebContents(web_contents());
278 manage_passwords_ui_controller
->ChooseCredential(password_form
,
280 state_
= password_manager::ui::INACTIVE_STATE
;
283 Profile
* ManagePasswordsBubbleModel::GetProfile() const {
284 return GetProfileFromWebContents(web_contents());
287 bool ManagePasswordsBubbleModel::IsNewUIActive() const {
288 return base::CommandLine::ForCurrentProcess()->HasSwitch(
289 switches::kEnableCredentialManagerAPI
);
293 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
294 return GetFieldWidth(USERNAME_FIELD
);
298 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
299 return GetFieldWidth(PASSWORD_FIELD
);
303 ManagePasswordsBubbleModel::PendingStateTitleBasedOnSavePasswordPref() const {
305 if (never_save_passwords_
)
306 message_id
= IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE
;
307 else if (IsNewUIActive())
308 message_id
= IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SMART_LOCK_PROMPT
;
310 message_id
= IDS_SAVE_PASSWORD
;
311 return l10n_util::GetStringUTF16(message_id
);