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/prefs/pref_service.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/password_manager/password_store_factory.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/signin/signin_manager_factory.h"
13 #include "chrome/browser/sync/profile_sync_service.h"
14 #include "chrome/browser/sync/profile_sync_service_factory.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_finder.h"
17 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
18 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
19 #include "chrome/common/url_constants.h"
20 #include "chrome/grit/chromium_strings.h"
21 #include "chrome/grit/generated_resources.h"
22 #include "components/feedback/feedback_data.h"
23 #include "components/feedback/feedback_util.h"
24 #include "components/password_manager/core/browser/password_bubble_experiment.h"
25 #include "components/password_manager/core/browser/password_store.h"
26 #include "components/password_manager/core/common/credential_manager_types.h"
27 #include "components/password_manager/core/common/password_manager_pref_names.h"
28 #include "components/password_manager/core/common/password_manager_ui.h"
29 #include "components/signin/core/browser/signin_manager.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/common/content_switches.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/resource/resource_bundle.h"
35 using autofill::PasswordFormMap
;
36 using feedback::FeedbackData
;
37 using content::WebContents
;
38 namespace metrics_util
= password_manager::metrics_util
;
42 enum FieldType
{ USERNAME_FIELD
, PASSWORD_FIELD
};
44 const int kUsernameFieldSize
= 30;
45 const int kPasswordFieldSize
= 22;
47 // Returns the width of |type| field.
48 int GetFieldWidth(FieldType type
) {
49 return ui::ResourceBundle::GetSharedInstance()
50 .GetFontList(ui::ResourceBundle::SmallFont
)
51 .GetExpectedTextWidth(type
== USERNAME_FIELD
? kUsernameFieldSize
52 : kPasswordFieldSize
);
55 Profile
* GetProfileFromWebContents(content::WebContents
* web_contents
) {
58 return Profile::FromBrowserContext(web_contents
->GetBrowserContext());
61 void RecordExperimentStatistics(content::WebContents
* web_contents
,
62 metrics_util::UIDismissalReason reason
) {
63 // TODO(vasilii): revive the function while implementing the smart bubble.
66 ScopedVector
<const autofill::PasswordForm
> DeepCopyForms(
67 const std::vector
<const autofill::PasswordForm
*>& forms
) {
68 ScopedVector
<const autofill::PasswordForm
> result
;
69 result
.reserve(forms
.size());
70 std::transform(forms
.begin(), forms
.end(), std::back_inserter(result
),
71 [](const autofill::PasswordForm
* form
) {
72 return new autofill::PasswordForm(*form
);
77 // A wrapper around password_bubble_experiment::IsSmartLockBrandingEnabled
78 // extracting the sync_service from the profile.
79 bool IsSmartLockBrandingEnabled(Profile
* profile
) {
80 const ProfileSyncService
* sync_service
=
81 ProfileSyncServiceFactory::GetForProfile(profile
);
82 return password_bubble_experiment::IsSmartLockBrandingEnabled(sync_service
);
87 ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
88 content::WebContents
* web_contents
)
89 : content::WebContentsObserver(web_contents
),
90 display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
91 dismissal_reason_(metrics_util::NOT_DISPLAYED
),
92 update_password_submission_event_(metrics_util::NO_UPDATE_SUBMISSION
) {
93 ManagePasswordsUIController
* controller
=
94 ManagePasswordsUIController::FromWebContents(web_contents
);
96 origin_
= controller
->origin();
97 state_
= controller
->state();
98 password_overridden_
= controller
->PasswordOverridden();
99 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
100 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
101 pending_password_
= controller
->PendingPassword();
102 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
103 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
104 // We don't need anything.
105 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
106 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
107 federated_credentials_
= DeepCopyForms(controller
->GetFederatedForms());
108 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
109 pending_password_
= *controller
->GetCurrentForms()[0];
111 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
114 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
115 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
116 UpdatePendingStateTitle();
117 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
119 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
120 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
121 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CHOOSE_TITLE
);
122 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
123 // There is no title.
124 } else if (state_
== password_manager::ui::MANAGE_STATE
) {
125 UpdateManageStateTitle();
128 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
129 base::string16 save_confirmation_link
=
130 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_LINK
);
131 int confirmation_text_id
= IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
;
132 if (IsSmartLockBrandingEnabled(GetProfile())) {
133 std::string management_hostname
=
134 GURL(chrome::kPasswordManagerAccountDashboardURL
).host();
135 save_confirmation_link
= base::UTF8ToUTF16(management_hostname
);
136 confirmation_text_id
=
137 IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_SMART_LOCK_TEXT
;
141 save_confirmation_text_
=
142 l10n_util::GetStringFUTF16(
143 confirmation_text_id
, save_confirmation_link
, &offset
);
144 save_confirmation_link_range_
=
145 gfx::Range(offset
, offset
+ save_confirmation_link
.length());
149 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
152 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
154 void ManagePasswordsBubbleModel::OnBubbleShown(
155 ManagePasswordsBubble::DisplayReason reason
) {
156 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
158 case password_manager::ui::PENDING_PASSWORD_STATE
:
159 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
161 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
:
162 display_disposition_
=
163 metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE
;
165 case password_manager::ui::MANAGE_STATE
:
166 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
173 case password_manager::ui::PENDING_PASSWORD_STATE
:
174 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
176 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
:
177 display_disposition_
=
178 metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE
;
180 case password_manager::ui::CONFIRMATION_STATE
:
181 display_disposition_
=
182 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
184 case password_manager::ui::CREDENTIAL_REQUEST_STATE
:
185 display_disposition_
= metrics_util::AUTOMATIC_CREDENTIAL_REQUEST
;
187 case password_manager::ui::AUTO_SIGNIN_STATE
:
188 display_disposition_
= metrics_util::AUTOMATIC_SIGNIN_TOAST
;
194 metrics_util::LogUIDisplayDisposition(display_disposition_
);
196 // Default to a dismissal reason of "no interaction". If the user interacts
197 // with the button in such a way that it closes, we'll reset this value
199 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
201 ManagePasswordsUIController
* controller
=
202 ManagePasswordsUIController::FromWebContents(web_contents());
203 controller
->OnBubbleShown();
206 void ManagePasswordsBubbleModel::OnBubbleHidden() {
207 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
208 Profile
* profile
= GetProfile();
209 if (profile
&& IsSmartLockBrandingEnabled(profile
)) {
210 profile
->GetPrefs()->SetBoolean(
211 password_manager::prefs::kWasSavePrompFirstRunExperienceShown
, true);
214 ManagePasswordsUIController
* manage_passwords_ui_controller
=
216 ManagePasswordsUIController::FromWebContents(web_contents())
218 if (manage_passwords_ui_controller
)
219 manage_passwords_ui_controller
->OnBubbleHidden();
220 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
223 if (state_
!= password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
224 // We have separate metrics for the Update bubble so do not record dismissal
226 metrics_util::LogUIDismissalReason(dismissal_reason_
);
228 // Other use cases have been reported in the callbacks like OnSaveClicked().
229 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
&&
230 dismissal_reason_
== metrics_util::NO_DIRECT_INTERACTION
)
231 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
232 // Check if this was update password and record update statistics.
233 if (update_password_submission_event_
== metrics_util::NO_UPDATE_SUBMISSION
&&
234 (state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
||
235 state_
== password_manager::ui::PENDING_PASSWORD_STATE
)) {
236 update_password_submission_event_
=
237 GetUpdateDismissalReason(NO_INTERACTION
);
239 if (update_password_submission_event_
!= metrics_util::NO_UPDATE_SUBMISSION
)
240 LogUpdatePasswordSubmissionEvent(update_password_submission_event_
);
243 void ManagePasswordsBubbleModel::OnCancelClicked() {
244 DCHECK_EQ(password_manager::ui::CREDENTIAL_REQUEST_STATE
, state_
);
245 dismissal_reason_
= metrics_util::CLICKED_CANCEL
;
248 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
249 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
250 update_password_submission_event_
= GetUpdateDismissalReason(NOPE_CLICKED
);
251 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
252 ManagePasswordsUIController
* manage_passwords_ui_controller
=
253 ManagePasswordsUIController::FromWebContents(web_contents());
254 manage_passwords_ui_controller
->NeverSavePassword();
257 void ManagePasswordsBubbleModel::OnSaveClicked() {
258 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
259 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
260 update_password_submission_event_
= GetUpdateDismissalReason(UPDATE_CLICKED
);
261 ManagePasswordsUIController
* manage_passwords_ui_controller
=
262 ManagePasswordsUIController::FromWebContents(web_contents());
263 manage_passwords_ui_controller
->SavePassword();
266 void ManagePasswordsBubbleModel::OnNopeUpdateClicked() {
267 update_password_submission_event_
= GetUpdateDismissalReason(NOPE_CLICKED
);
270 void ManagePasswordsBubbleModel::OnUpdateClicked(
271 const autofill::PasswordForm
& password_form
) {
272 update_password_submission_event_
= GetUpdateDismissalReason(UPDATE_CLICKED
);
273 ManagePasswordsUIController
* manage_passwords_ui_controller
=
274 ManagePasswordsUIController::FromWebContents(web_contents());
275 manage_passwords_ui_controller
->UpdatePassword(password_form
);
278 void ManagePasswordsBubbleModel::OnDoneClicked() {
279 dismissal_reason_
= metrics_util::CLICKED_DONE
;
282 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
283 // User intent is pretty similar in both cases.
284 void ManagePasswordsBubbleModel::OnOKClicked() {
285 dismissal_reason_
= metrics_util::CLICKED_OK
;
288 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
289 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
290 if (IsSmartLockBrandingEnabled(GetProfile())) {
291 ManagePasswordsUIController::FromWebContents(web_contents())
292 ->NavigateToExternalPasswordManager();
294 ManagePasswordsUIController::FromWebContents(web_contents())
295 ->NavigateToPasswordManagerSettingsPage();
299 void ManagePasswordsBubbleModel::OnBrandLinkClicked() {
300 dismissal_reason_
= metrics_util::CLICKED_BRAND_NAME
;
301 ManagePasswordsUIController::FromWebContents(web_contents())
302 ->NavigateToSmartLockPage();
305 void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() {
306 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT
;
309 void ManagePasswordsBubbleModel::OnAutoSignInClicked() {
310 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_CLICKED
;
311 ManagePasswordsUIController
* manage_passwords_ui_controller
=
312 ManagePasswordsUIController::FromWebContents(web_contents());
313 manage_passwords_ui_controller
->ManageAccounts();
316 void ManagePasswordsBubbleModel::OnPasswordAction(
317 const autofill::PasswordForm
& password_form
,
318 PasswordAction action
) {
322 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
323 password_manager::PasswordStore
* password_store
=
324 PasswordStoreFactory::GetForProfile(
325 profile
, ServiceAccessType::EXPLICIT_ACCESS
).get();
326 DCHECK(password_store
);
327 if (action
== REMOVE_PASSWORD
)
328 password_store
->RemoveLogin(password_form
);
330 password_store
->AddLogin(password_form
);
333 void ManagePasswordsBubbleModel::OnChooseCredentials(
334 const autofill::PasswordForm
& password_form
,
335 password_manager::CredentialType credential_type
) {
336 dismissal_reason_
= metrics_util::CLICKED_CREDENTIAL
;
337 ManagePasswordsUIController
* manage_passwords_ui_controller
=
338 ManagePasswordsUIController::FromWebContents(web_contents());
339 manage_passwords_ui_controller
->ChooseCredential(password_form
,
343 Profile
* ManagePasswordsBubbleModel::GetProfile() const {
344 return GetProfileFromWebContents(web_contents());
347 bool ManagePasswordsBubbleModel::ShouldShowMultipleAccountUpdateUI() const {
348 ManagePasswordsUIController
* controller
=
349 ManagePasswordsUIController::FromWebContents(web_contents());
350 return controller
->ShouldShowMultipleAccountUpdateUI();
353 bool ManagePasswordsBubbleModel::ShouldShowGoogleSmartLockWelcome() const {
354 Profile
* profile
= GetProfile();
355 if (IsSmartLockBrandingEnabled(profile
)) {
356 PrefService
* prefs
= profile
->GetPrefs();
357 return !prefs
->GetBoolean(
358 password_manager::prefs::kWasSavePrompFirstRunExperienceShown
);
364 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
365 return GetFieldWidth(USERNAME_FIELD
);
369 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
370 return GetFieldWidth(PASSWORD_FIELD
);
373 void ManagePasswordsBubbleModel::UpdatePendingStateTitle() {
374 title_brand_link_range_
= gfx::Range();
375 GetSavePasswordDialogTitleTextAndLinkRange(
376 web_contents()->GetVisibleURL(), origin(),
377 IsSmartLockBrandingEnabled(GetProfile()),
378 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
, &title_
,
379 &title_brand_link_range_
);
382 void ManagePasswordsBubbleModel::UpdateManageStateTitle() {
383 GetManagePasswordsDialogTitleText(web_contents()->GetVisibleURL(), origin(),
387 password_manager::metrics_util::UpdatePasswordSubmissionEvent
388 ManagePasswordsBubbleModel::GetUpdateDismissalReason(
389 UserBehaviorOnUpdateBubble behavior
) const {
390 using namespace password_manager::metrics_util
;
391 static const password_manager::metrics_util::UpdatePasswordSubmissionEvent
392 update_events
[4][3] = {
393 {NO_ACCOUNTS_CLICKED_UPDATE
, NO_ACCOUNTS_CLICKED_NOPE
,
394 NO_ACCOUNTS_NO_INTERACTION
},
395 {ONE_ACCOUNT_CLICKED_UPDATE
, ONE_ACCOUNT_CLICKED_NOPE
,
396 ONE_ACCOUNT_NO_INTERACTION
},
397 {MULTIPLE_ACCOUNTS_CLICKED_UPDATE
, MULTIPLE_ACCOUNTS_CLICKED_NOPE
,
398 MULTIPLE_ACCOUNTS_NO_INTERACTION
},
399 {PASSWORD_OVERRIDDEN_CLICKED_UPDATE
, PASSWORD_OVERRIDDEN_CLICKED_NOPE
,
400 PASSWORD_OVERRIDDEN_NO_INTERACTION
}};
402 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
403 if (pending_password_
.IsPossibleChangePasswordFormWithoutUsername())
404 return update_events
[0][behavior
];
405 return NO_UPDATE_SUBMISSION
;
407 if (state_
!= password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
)
408 return NO_UPDATE_SUBMISSION
;
409 if (password_overridden_
)
410 return update_events
[3][behavior
];
411 if (ShouldShowMultipleAccountUpdateUI())
412 return update_events
[2][behavior
];
413 return update_events
[1][behavior
];