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/content/common/credential_manager_types.h"
19 #include "components/password_manager/core/browser/password_store.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 base::string16
PendingStateTitleBasedOnSavePasswordPref(
61 bool never_save_passwords
) {
62 return l10n_util::GetStringUTF16(
63 never_save_passwords
? IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE
67 ScopedVector
<const autofill::PasswordForm
> DeepCopyForms(
68 const std::vector
<const autofill::PasswordForm
*>& forms
) {
69 ScopedVector
<const autofill::PasswordForm
> result
;
70 result
.reserve(forms
.size());
71 std::transform(forms
.begin(), forms
.end(), std::back_inserter(result
),
72 [](const autofill::PasswordForm
* form
) {
73 return new autofill::PasswordForm(*form
);
80 ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
81 content::WebContents
* web_contents
)
82 : content::WebContentsObserver(web_contents
),
83 never_save_passwords_(false),
84 display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
85 dismissal_reason_(metrics_util::NOT_DISPLAYED
) {
86 ManagePasswordsUIController
* controller
=
87 ManagePasswordsUIController::FromWebContents(web_contents
);
89 origin_
= controller
->origin();
90 state_
= controller
->state();
91 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
92 pending_password_
= controller
->PendingPassword();
93 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
94 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
95 // We don't need anything.
96 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
97 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
98 federated_credentials_
= DeepCopyForms(controller
->GetFederatedForms());
99 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
100 pending_password_
= *controller
->GetCurrentForms()[0];
102 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
105 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
106 title_
= PendingStateTitleBasedOnSavePasswordPref(never_save_passwords_
);
107 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
108 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED_TITLE
);
109 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
111 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
112 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
113 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CHOOSE_TITLE
);
114 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
115 // There is no title.
117 title_
= IsNewUIActive() ?
118 l10n_util::GetStringFUTF16(IDS_MANAGE_ACCOUNTS_TITLE
,
119 base::UTF8ToUTF16(origin_
.spec())) :
120 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE
);
123 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
124 base::string16 save_confirmation_link
=
125 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_LINK
);
127 save_confirmation_text_
=
128 l10n_util::GetStringFUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
,
129 save_confirmation_link
, &offset
);
130 save_confirmation_link_range_
=
131 gfx::Range(offset
, offset
+ save_confirmation_link
.length());
135 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
138 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
140 void ManagePasswordsBubbleModel::OnBubbleShown(
141 ManagePasswordsBubble::DisplayReason reason
) {
142 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
143 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
144 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
145 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
146 display_disposition_
= metrics_util::MANUAL_BLACKLISTED
;
148 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
151 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
152 display_disposition_
=
153 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
154 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
155 display_disposition_
= metrics_util::AUTOMATIC_CREDENTIAL_REQUEST
;
156 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
157 display_disposition_
= metrics_util::AUTOMATIC_SIGNIN_TOAST
;
159 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
162 metrics_util::LogUIDisplayDisposition(display_disposition_
);
164 // Default to a dismissal reason of "no interaction". If the user interacts
165 // with the button in such a way that it closes, we'll reset this value
167 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
169 ManagePasswordsUIController
* controller
=
170 ManagePasswordsUIController::FromWebContents(web_contents());
171 controller
->OnBubbleShown();
174 void ManagePasswordsBubbleModel::OnBubbleHidden() {
175 ManagePasswordsUIController
* manage_passwords_ui_controller
=
177 ManagePasswordsUIController::FromWebContents(web_contents())
179 if (manage_passwords_ui_controller
)
180 manage_passwords_ui_controller
->OnBubbleHidden();
181 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
184 metrics_util::LogUIDismissalReason(dismissal_reason_
);
185 // Other use cases have been reported in the callbacks like OnSaveClicked().
186 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
&&
187 dismissal_reason_
== metrics_util::NO_DIRECT_INTERACTION
)
188 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
191 void ManagePasswordsBubbleModel::OnNopeClicked() {
192 dismissal_reason_
= metrics_util::CLICKED_NOPE
;
193 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
194 if (state_
!= password_manager::ui::CREDENTIAL_REQUEST_STATE
)
195 state_
= password_manager::ui::PENDING_PASSWORD_STATE
;
198 void ManagePasswordsBubbleModel::OnConfirmationForNeverForThisSite() {
199 never_save_passwords_
= true;
200 title_
= PendingStateTitleBasedOnSavePasswordPref(never_save_passwords_
);
203 void ManagePasswordsBubbleModel::OnUndoNeverForThisSite() {
204 never_save_passwords_
= false;
205 title_
= PendingStateTitleBasedOnSavePasswordPref(never_save_passwords_
);
208 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
209 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
210 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
211 ManagePasswordsUIController
* manage_passwords_ui_controller
=
212 ManagePasswordsUIController::FromWebContents(web_contents());
213 manage_passwords_ui_controller
->NeverSavePassword();
214 state_
= password_manager::ui::BLACKLIST_STATE
;
217 void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
218 dismissal_reason_
= metrics_util::CLICKED_UNBLACKLIST
;
219 ManagePasswordsUIController
* manage_passwords_ui_controller
=
220 ManagePasswordsUIController::FromWebContents(web_contents());
221 manage_passwords_ui_controller
->UnblacklistSite();
222 state_
= password_manager::ui::MANAGE_STATE
;
225 void ManagePasswordsBubbleModel::OnSaveClicked() {
226 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
227 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
228 ManagePasswordsUIController
* manage_passwords_ui_controller
=
229 ManagePasswordsUIController::FromWebContents(web_contents());
230 manage_passwords_ui_controller
->SavePassword();
231 state_
= password_manager::ui::MANAGE_STATE
;
234 void ManagePasswordsBubbleModel::OnDoneClicked() {
235 dismissal_reason_
= metrics_util::CLICKED_DONE
;
238 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
239 // User intent is pretty similar in both cases.
240 void ManagePasswordsBubbleModel::OnOKClicked() {
241 dismissal_reason_
= metrics_util::CLICKED_OK
;
244 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
245 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
246 ManagePasswordsUIController::FromWebContents(web_contents())
247 ->NavigateToPasswordManagerSettingsPage();
250 void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() {
251 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT
;
254 void ManagePasswordsBubbleModel::OnAutoSignInClicked() {
255 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_CLICKED
;
256 ManagePasswordsUIController
* manage_passwords_ui_controller
=
257 ManagePasswordsUIController::FromWebContents(web_contents());
258 manage_passwords_ui_controller
->ManageAccounts();
259 state_
= password_manager::ui::MANAGE_STATE
;
262 void ManagePasswordsBubbleModel::OnPasswordAction(
263 const autofill::PasswordForm
& password_form
,
264 PasswordAction action
) {
268 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
269 password_manager::PasswordStore
* password_store
=
270 PasswordStoreFactory::GetForProfile(
271 profile
, ServiceAccessType::EXPLICIT_ACCESS
).get();
272 DCHECK(password_store
);
273 if (action
== REMOVE_PASSWORD
)
274 password_store
->RemoveLogin(password_form
);
276 password_store
->AddLogin(password_form
);
279 void ManagePasswordsBubbleModel::OnChooseCredentials(
280 const autofill::PasswordForm
& password_form
,
281 password_manager::CredentialType credential_type
) {
282 dismissal_reason_
= metrics_util::CLICKED_CREDENTIAL
;
283 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
284 ManagePasswordsUIController
* manage_passwords_ui_controller
=
285 ManagePasswordsUIController::FromWebContents(web_contents());
286 manage_passwords_ui_controller
->ChooseCredential(password_form
,
288 state_
= password_manager::ui::INACTIVE_STATE
;
291 Profile
* ManagePasswordsBubbleModel::GetProfile() const {
292 return GetProfileFromWebContents(web_contents());
295 bool ManagePasswordsBubbleModel::IsNewUIActive() const {
296 return base::CommandLine::ForCurrentProcess()->HasSwitch(
297 switches::kEnableCredentialManagerAPI
);
301 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
302 return GetFieldWidth(USERNAME_FIELD
);
306 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
307 return GetFieldWidth(PASSWORD_FIELD
);