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/signin/signin_manager_factory.h"
12 #include "chrome/browser/sync/profile_sync_service.h"
13 #include "chrome/browser/sync/profile_sync_service_factory.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_finder.h"
16 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
17 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
18 #include "chrome/common/url_constants.h"
19 #include "chrome/grit/chromium_strings.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/feedback/feedback_data.h"
22 #include "components/feedback/feedback_util.h"
23 #include "components/password_manager/core/browser/password_bubble_experiment.h"
24 #include "components/password_manager/core/browser/password_store.h"
25 #include "components/password_manager/core/common/credential_manager_types.h"
26 #include "components/password_manager/core/common/password_manager_ui.h"
27 #include "components/signin/core/browser/signin_manager.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/common/content_switches.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/resource/resource_bundle.h"
33 using autofill::PasswordFormMap
;
34 using feedback::FeedbackData
;
35 using content::WebContents
;
36 namespace metrics_util
= password_manager::metrics_util
;
40 enum FieldType
{ USERNAME_FIELD
, PASSWORD_FIELD
};
42 const int kUsernameFieldSize
= 30;
43 const int kPasswordFieldSize
= 22;
45 // Returns the width of |type| field.
46 int GetFieldWidth(FieldType type
) {
47 return ui::ResourceBundle::GetSharedInstance()
48 .GetFontList(ui::ResourceBundle::SmallFont
)
49 .GetExpectedTextWidth(type
== USERNAME_FIELD
? kUsernameFieldSize
50 : kPasswordFieldSize
);
53 Profile
* GetProfileFromWebContents(content::WebContents
* web_contents
) {
56 return Profile::FromBrowserContext(web_contents
->GetBrowserContext());
59 void RecordExperimentStatistics(content::WebContents
* web_contents
,
60 metrics_util::UIDismissalReason reason
) {
61 // TODO(vasilii): revive the function while implementing the smart bubble.
64 ScopedVector
<const autofill::PasswordForm
> DeepCopyForms(
65 const std::vector
<const autofill::PasswordForm
*>& forms
) {
66 ScopedVector
<const autofill::PasswordForm
> result
;
67 result
.reserve(forms
.size());
68 std::transform(forms
.begin(), forms
.end(), std::back_inserter(result
),
69 [](const autofill::PasswordForm
* form
) {
70 return new autofill::PasswordForm(*form
);
75 // A wrapper around password_bubble_experiment::IsSmartLockBrandingEnabled
76 // extracting the sync_service from the profile.
77 bool IsSmartLockBrandingEnabled(Profile
* profile
) {
78 const ProfileSyncService
* sync_service
=
79 ProfileSyncServiceFactory::GetForProfile(profile
);
80 return password_bubble_experiment::IsSmartLockBrandingEnabled(sync_service
);
85 ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
86 content::WebContents
* web_contents
)
87 : content::WebContentsObserver(web_contents
),
88 display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
89 dismissal_reason_(metrics_util::NOT_DISPLAYED
),
90 update_password_submission_event_(metrics_util::NO_UPDATE_SUBMISSION
) {
91 ManagePasswordsUIController
* controller
=
92 ManagePasswordsUIController::FromWebContents(web_contents
);
94 origin_
= controller
->origin();
95 state_
= controller
->state();
96 password_overridden_
= controller
->PasswordOverridden();
97 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
98 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
99 pending_password_
= controller
->PendingPassword();
100 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
101 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
102 // We don't need anything.
103 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
104 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
105 federated_credentials_
= DeepCopyForms(controller
->GetFederatedForms());
106 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
107 pending_password_
= *controller
->GetCurrentForms()[0];
109 local_credentials_
= DeepCopyForms(controller
->GetCurrentForms());
112 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
113 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
114 UpdatePendingStateTitle();
115 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
117 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
118 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
119 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CHOOSE_TITLE
);
120 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
121 // There is no title.
123 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE
);
126 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
127 base::string16 save_confirmation_link
=
128 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_LINK
);
129 int confirmation_text_id
= IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
;
130 if (IsSmartLockBrandingEnabled(GetProfile())) {
131 std::string management_hostname
=
132 GURL(chrome::kPasswordManagerAccountDashboardURL
).host();
133 save_confirmation_link
= base::UTF8ToUTF16(management_hostname
);
134 confirmation_text_id
=
135 IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_SMART_LOCK_TEXT
;
139 save_confirmation_text_
=
140 l10n_util::GetStringFUTF16(
141 confirmation_text_id
, save_confirmation_link
, &offset
);
142 save_confirmation_link_range_
=
143 gfx::Range(offset
, offset
+ save_confirmation_link
.length());
147 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
150 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
152 void ManagePasswordsBubbleModel::OnBubbleShown(
153 ManagePasswordsBubble::DisplayReason reason
) {
154 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
156 case password_manager::ui::PENDING_PASSWORD_STATE
:
157 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
159 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
:
160 display_disposition_
=
161 metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE
;
163 case password_manager::ui::MANAGE_STATE
:
164 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
171 case password_manager::ui::PENDING_PASSWORD_STATE
:
172 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
174 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
:
175 display_disposition_
=
176 metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE
;
178 case password_manager::ui::CONFIRMATION_STATE
:
179 display_disposition_
=
180 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
182 case password_manager::ui::CREDENTIAL_REQUEST_STATE
:
183 display_disposition_
= metrics_util::AUTOMATIC_CREDENTIAL_REQUEST
;
185 case password_manager::ui::AUTO_SIGNIN_STATE
:
186 display_disposition_
= metrics_util::AUTOMATIC_SIGNIN_TOAST
;
192 metrics_util::LogUIDisplayDisposition(display_disposition_
);
194 // Default to a dismissal reason of "no interaction". If the user interacts
195 // with the button in such a way that it closes, we'll reset this value
197 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
199 ManagePasswordsUIController
* controller
=
200 ManagePasswordsUIController::FromWebContents(web_contents());
201 controller
->OnBubbleShown();
204 void ManagePasswordsBubbleModel::OnBubbleHidden() {
205 ManagePasswordsUIController
* manage_passwords_ui_controller
=
207 ManagePasswordsUIController::FromWebContents(web_contents())
209 if (manage_passwords_ui_controller
)
210 manage_passwords_ui_controller
->OnBubbleHidden();
211 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
214 if (state_
!= password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
) {
215 // We have separate metrics for the Update bubble so do not record dismissal
217 metrics_util::LogUIDismissalReason(dismissal_reason_
);
219 // Other use cases have been reported in the callbacks like OnSaveClicked().
220 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
&&
221 dismissal_reason_
== metrics_util::NO_DIRECT_INTERACTION
)
222 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
223 // Check if this was update password and record update statistics.
224 if (update_password_submission_event_
== metrics_util::NO_UPDATE_SUBMISSION
&&
225 (state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
||
226 state_
== password_manager::ui::PENDING_PASSWORD_STATE
)) {
227 update_password_submission_event_
=
228 GetUpdateDismissalReason(NO_INTERACTION
);
230 if (update_password_submission_event_
!= metrics_util::NO_UPDATE_SUBMISSION
)
231 LogUpdatePasswordSubmissionEvent(update_password_submission_event_
);
234 void ManagePasswordsBubbleModel::OnCancelClicked() {
235 DCHECK_EQ(password_manager::ui::CREDENTIAL_REQUEST_STATE
, state_
);
236 dismissal_reason_
= metrics_util::CLICKED_CANCEL
;
239 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
240 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
241 update_password_submission_event_
= GetUpdateDismissalReason(NOPE_CLICKED
);
242 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
243 ManagePasswordsUIController
* manage_passwords_ui_controller
=
244 ManagePasswordsUIController::FromWebContents(web_contents());
245 manage_passwords_ui_controller
->NeverSavePassword();
248 void ManagePasswordsBubbleModel::OnSaveClicked() {
249 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
250 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
251 update_password_submission_event_
= GetUpdateDismissalReason(UPDATE_CLICKED
);
252 ManagePasswordsUIController
* manage_passwords_ui_controller
=
253 ManagePasswordsUIController::FromWebContents(web_contents());
254 manage_passwords_ui_controller
->SavePassword();
257 void ManagePasswordsBubbleModel::OnNopeUpdateClicked() {
258 update_password_submission_event_
= GetUpdateDismissalReason(NOPE_CLICKED
);
261 void ManagePasswordsBubbleModel::OnUpdateClicked(
262 const autofill::PasswordForm
& password_form
) {
263 update_password_submission_event_
= GetUpdateDismissalReason(UPDATE_CLICKED
);
264 ManagePasswordsUIController
* manage_passwords_ui_controller
=
265 ManagePasswordsUIController::FromWebContents(web_contents());
266 manage_passwords_ui_controller
->UpdatePassword(password_form
);
269 void ManagePasswordsBubbleModel::OnDoneClicked() {
270 dismissal_reason_
= metrics_util::CLICKED_DONE
;
273 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
274 // User intent is pretty similar in both cases.
275 void ManagePasswordsBubbleModel::OnOKClicked() {
276 dismissal_reason_
= metrics_util::CLICKED_OK
;
279 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
280 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
281 if (IsSmartLockBrandingEnabled(GetProfile())) {
282 ManagePasswordsUIController::FromWebContents(web_contents())
283 ->NavigateToExternalPasswordManager();
285 ManagePasswordsUIController::FromWebContents(web_contents())
286 ->NavigateToPasswordManagerSettingsPage();
290 void ManagePasswordsBubbleModel::OnBrandLinkClicked() {
291 dismissal_reason_
= metrics_util::CLICKED_BRAND_NAME
;
292 ManagePasswordsUIController::FromWebContents(web_contents())
293 ->NavigateToSmartLockPage();
296 void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() {
297 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT
;
300 void ManagePasswordsBubbleModel::OnAutoSignInClicked() {
301 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_CLICKED
;
302 ManagePasswordsUIController
* manage_passwords_ui_controller
=
303 ManagePasswordsUIController::FromWebContents(web_contents());
304 manage_passwords_ui_controller
->ManageAccounts();
307 void ManagePasswordsBubbleModel::OnPasswordAction(
308 const autofill::PasswordForm
& password_form
,
309 PasswordAction action
) {
313 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
314 password_manager::PasswordStore
* password_store
=
315 PasswordStoreFactory::GetForProfile(
316 profile
, ServiceAccessType::EXPLICIT_ACCESS
).get();
317 DCHECK(password_store
);
318 if (action
== REMOVE_PASSWORD
)
319 password_store
->RemoveLogin(password_form
);
321 password_store
->AddLogin(password_form
);
324 void ManagePasswordsBubbleModel::OnChooseCredentials(
325 const autofill::PasswordForm
& password_form
,
326 password_manager::CredentialType credential_type
) {
327 dismissal_reason_
= metrics_util::CLICKED_CREDENTIAL
;
328 ManagePasswordsUIController
* manage_passwords_ui_controller
=
329 ManagePasswordsUIController::FromWebContents(web_contents());
330 manage_passwords_ui_controller
->ChooseCredential(password_form
,
334 Profile
* ManagePasswordsBubbleModel::GetProfile() const {
335 return GetProfileFromWebContents(web_contents());
338 bool ManagePasswordsBubbleModel::ShouldShowMultipleAccountUpdateUI() const {
339 ManagePasswordsUIController
* controller
=
340 ManagePasswordsUIController::FromWebContents(web_contents());
341 return controller
->ShouldShowMultipleAccountUpdateUI();
345 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
346 return GetFieldWidth(USERNAME_FIELD
);
350 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
351 return GetFieldWidth(PASSWORD_FIELD
);
354 void ManagePasswordsBubbleModel::UpdatePendingStateTitle() {
355 title_brand_link_range_
= gfx::Range();
356 GetSavePasswordDialogTitleTextAndLinkRange(
357 web_contents()->GetVisibleURL(), origin(),
358 IsSmartLockBrandingEnabled(GetProfile()),
359 state_
== password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
, &title_
,
360 &title_brand_link_range_
);
363 password_manager::metrics_util::UpdatePasswordSubmissionEvent
364 ManagePasswordsBubbleModel::GetUpdateDismissalReason(
365 UserBehaviorOnUpdateBubble behavior
) const {
366 using namespace password_manager::metrics_util
;
367 static const password_manager::metrics_util::UpdatePasswordSubmissionEvent
368 update_events
[4][3] = {
369 {NO_ACCOUNTS_CLICKED_UPDATE
, NO_ACCOUNTS_CLICKED_NOPE
,
370 NO_ACCOUNTS_NO_INTERACTION
},
371 {ONE_ACCOUNT_CLICKED_UPDATE
, ONE_ACCOUNT_CLICKED_NOPE
,
372 ONE_ACCOUNT_NO_INTERACTION
},
373 {MULTIPLE_ACCOUNTS_CLICKED_UPDATE
, MULTIPLE_ACCOUNTS_CLICKED_NOPE
,
374 MULTIPLE_ACCOUNTS_NO_INTERACTION
},
375 {PASSWORD_OVERRIDDEN_CLICKED_UPDATE
, PASSWORD_OVERRIDDEN_CLICKED_NOPE
,
376 PASSWORD_OVERRIDDEN_NO_INTERACTION
}};
378 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
379 if (pending_password_
.IsPossibleChangePasswordFormWithoutUsername())
380 return update_events
[0][behavior
];
381 return NO_UPDATE_SUBMISSION
;
383 if (state_
!= password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
)
384 return NO_UPDATE_SUBMISSION
;
385 if (password_overridden_
)
386 return update_events
[3][behavior
];
387 if (ShouldShowMultipleAccountUpdateUI())
388 return update_events
[2][behavior
];
389 return update_events
[1][behavior
];