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/common/url_constants.h"
18 #include "chrome/grit/chromium_strings.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/feedback/feedback_data.h"
21 #include "components/feedback/feedback_util.h"
22 #include "components/password_manager/core/browser/password_bubble_experiment.h"
23 #include "components/password_manager/core/browser/password_store.h"
24 #include "components/password_manager/core/common/credential_manager_types.h"
25 #include "components/password_manager/core/common/password_manager_ui.h"
26 #include "components/signin/core/browser/signin_manager.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/common/content_switches.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
32 using autofill::PasswordFormMap
;
33 using feedback::FeedbackData
;
34 using content::WebContents
;
35 namespace metrics_util
= password_manager::metrics_util
;
39 enum FieldType
{ USERNAME_FIELD
, PASSWORD_FIELD
};
41 const int kUsernameFieldSize
= 30;
42 const int kPasswordFieldSize
= 22;
44 // Returns the width of |type| field.
45 int GetFieldWidth(FieldType type
) {
46 return ui::ResourceBundle::GetSharedInstance()
47 .GetFontList(ui::ResourceBundle::SmallFont
)
48 .GetExpectedTextWidth(type
== USERNAME_FIELD
? kUsernameFieldSize
49 : kPasswordFieldSize
);
52 Profile
* GetProfileFromWebContents(content::WebContents
* web_contents
) {
55 return Profile::FromBrowserContext(web_contents
->GetBrowserContext());
58 void RecordExperimentStatistics(content::WebContents
* web_contents
,
59 metrics_util::UIDismissalReason reason
) {
60 Profile
* profile
= GetProfileFromWebContents(web_contents
);
63 password_bubble_experiment::RecordBubbleClosed(profile
->GetPrefs(), reason
);
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 never_save_passwords_(false),
91 display_disposition_(metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
),
92 dismissal_reason_(metrics_util::NOT_DISPLAYED
) {
93 ManagePasswordsUIController
* controller
=
94 ManagePasswordsUIController::FromWebContents(web_contents
);
96 origin_
= controller
->origin();
97 state_
= controller
->state();
98 if (state_
== password_manager::ui::PENDING_PASSWORD_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 UpdatePendingStateTitle();
114 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
115 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED_TITLE
);
116 } else if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
118 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE
);
119 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
120 title_
= l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CHOOSE_TITLE
);
121 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
122 // There is no title.
124 title_
= IsNewUIActive() ?
125 l10n_util::GetStringFUTF16(IDS_MANAGE_ACCOUNTS_TITLE
,
126 base::UTF8ToUTF16(origin_
.spec())) :
127 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE
);
130 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
131 base::string16 save_confirmation_link
=
132 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_LINK
);
133 int confirmation_text_id
= IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT
;
134 if (IsSmartLockBrandingEnabled(GetProfile())) {
135 std::string management_hostname
=
136 GURL(chrome::kPasswordManagerAccountDashboardURL
).host();
137 save_confirmation_link
= base::UTF8ToUTF16(management_hostname
);
138 confirmation_text_id
=
139 IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_SMART_LOCK_TEXT
;
143 save_confirmation_text_
=
144 l10n_util::GetStringFUTF16(
145 confirmation_text_id
, save_confirmation_link
, &offset
);
146 save_confirmation_link_range_
=
147 gfx::Range(offset
, offset
+ save_confirmation_link
.length());
151 l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK
);
154 ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
156 void ManagePasswordsBubbleModel::OnBubbleShown(
157 ManagePasswordsBubble::DisplayReason reason
) {
158 if (reason
== ManagePasswordsBubble::USER_ACTION
) {
159 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
) {
160 display_disposition_
= metrics_util::MANUAL_WITH_PASSWORD_PENDING
;
161 } else if (state_
== password_manager::ui::BLACKLIST_STATE
) {
162 display_disposition_
= metrics_util::MANUAL_BLACKLISTED
;
164 display_disposition_
= metrics_util::MANUAL_MANAGE_PASSWORDS
;
167 if (state_
== password_manager::ui::CONFIRMATION_STATE
) {
168 display_disposition_
=
169 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION
;
170 } else if (state_
== password_manager::ui::CREDENTIAL_REQUEST_STATE
) {
171 display_disposition_
= metrics_util::AUTOMATIC_CREDENTIAL_REQUEST
;
172 } else if (state_
== password_manager::ui::AUTO_SIGNIN_STATE
) {
173 display_disposition_
= metrics_util::AUTOMATIC_SIGNIN_TOAST
;
175 display_disposition_
= metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING
;
178 metrics_util::LogUIDisplayDisposition(display_disposition_
);
180 // Default to a dismissal reason of "no interaction". If the user interacts
181 // with the button in such a way that it closes, we'll reset this value
183 dismissal_reason_
= metrics_util::NO_DIRECT_INTERACTION
;
185 ManagePasswordsUIController
* controller
=
186 ManagePasswordsUIController::FromWebContents(web_contents());
187 controller
->OnBubbleShown();
190 void ManagePasswordsBubbleModel::OnBubbleHidden() {
191 ManagePasswordsUIController
* manage_passwords_ui_controller
=
193 ManagePasswordsUIController::FromWebContents(web_contents())
195 if (manage_passwords_ui_controller
)
196 manage_passwords_ui_controller
->OnBubbleHidden();
197 if (dismissal_reason_
== metrics_util::NOT_DISPLAYED
)
200 metrics_util::LogUIDismissalReason(dismissal_reason_
);
201 // Other use cases have been reported in the callbacks like OnSaveClicked().
202 if (state_
== password_manager::ui::PENDING_PASSWORD_STATE
&&
203 dismissal_reason_
== metrics_util::NO_DIRECT_INTERACTION
)
204 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
207 void ManagePasswordsBubbleModel::OnNopeClicked() {
208 dismissal_reason_
= metrics_util::CLICKED_NOPE
;
209 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
210 if (state_
!= password_manager::ui::CREDENTIAL_REQUEST_STATE
)
211 state_
= password_manager::ui::PENDING_PASSWORD_STATE
;
214 void ManagePasswordsBubbleModel::OnConfirmationForNeverForThisSite() {
215 never_save_passwords_
= true;
216 UpdatePendingStateTitle();
219 void ManagePasswordsBubbleModel::OnUndoNeverForThisSite() {
220 never_save_passwords_
= false;
221 UpdatePendingStateTitle();
224 void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
225 dismissal_reason_
= metrics_util::CLICKED_NEVER
;
226 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
227 ManagePasswordsUIController
* manage_passwords_ui_controller
=
228 ManagePasswordsUIController::FromWebContents(web_contents());
229 manage_passwords_ui_controller
->NeverSavePassword();
230 state_
= password_manager::ui::BLACKLIST_STATE
;
233 void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
234 dismissal_reason_
= metrics_util::CLICKED_UNBLACKLIST
;
235 ManagePasswordsUIController
* manage_passwords_ui_controller
=
236 ManagePasswordsUIController::FromWebContents(web_contents());
237 manage_passwords_ui_controller
->UnblacklistSite();
238 state_
= password_manager::ui::MANAGE_STATE
;
241 void ManagePasswordsBubbleModel::OnSaveClicked() {
242 dismissal_reason_
= metrics_util::CLICKED_SAVE
;
243 RecordExperimentStatistics(web_contents(), dismissal_reason_
);
244 ManagePasswordsUIController
* manage_passwords_ui_controller
=
245 ManagePasswordsUIController::FromWebContents(web_contents());
246 manage_passwords_ui_controller
->SavePassword();
247 state_
= password_manager::ui::MANAGE_STATE
;
250 void ManagePasswordsBubbleModel::OnDoneClicked() {
251 dismissal_reason_
= metrics_util::CLICKED_DONE
;
254 // TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
255 // User intent is pretty similar in both cases.
256 void ManagePasswordsBubbleModel::OnOKClicked() {
257 dismissal_reason_
= metrics_util::CLICKED_OK
;
260 void ManagePasswordsBubbleModel::OnManageLinkClicked() {
261 dismissal_reason_
= metrics_util::CLICKED_MANAGE
;
262 if (IsSmartLockBrandingEnabled(GetProfile())) {
263 ManagePasswordsUIController::FromWebContents(web_contents())
264 ->NavigateToExternalPasswordManager();
266 ManagePasswordsUIController::FromWebContents(web_contents())
267 ->NavigateToPasswordManagerSettingsPage();
271 void ManagePasswordsBubbleModel::OnBrandLinkClicked() {
272 dismissal_reason_
= metrics_util::CLICKED_BRAND_NAME
;
273 ManagePasswordsUIController::FromWebContents(web_contents())
274 ->NavigateToSmartLockHelpArticle();
277 void ManagePasswordsBubbleModel::OnAutoSignInToastTimeout() {
278 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_TIMEOUT
;
281 void ManagePasswordsBubbleModel::OnAutoSignInClicked() {
282 dismissal_reason_
= metrics_util::AUTO_SIGNIN_TOAST_CLICKED
;
283 ManagePasswordsUIController
* manage_passwords_ui_controller
=
284 ManagePasswordsUIController::FromWebContents(web_contents());
285 manage_passwords_ui_controller
->ManageAccounts();
286 state_
= password_manager::ui::MANAGE_STATE
;
289 void ManagePasswordsBubbleModel::OnPasswordAction(
290 const autofill::PasswordForm
& password_form
,
291 PasswordAction action
) {
295 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
296 password_manager::PasswordStore
* password_store
=
297 PasswordStoreFactory::GetForProfile(
298 profile
, ServiceAccessType::EXPLICIT_ACCESS
).get();
299 DCHECK(password_store
);
300 if (action
== REMOVE_PASSWORD
)
301 password_store
->RemoveLogin(password_form
);
303 password_store
->AddLogin(password_form
);
306 void ManagePasswordsBubbleModel::OnChooseCredentials(
307 const autofill::PasswordForm
& password_form
,
308 password_manager::CredentialType credential_type
) {
309 dismissal_reason_
= metrics_util::CLICKED_CREDENTIAL
;
310 ManagePasswordsUIController
* manage_passwords_ui_controller
=
311 ManagePasswordsUIController::FromWebContents(web_contents());
312 manage_passwords_ui_controller
->ChooseCredential(password_form
,
314 state_
= password_manager::ui::INACTIVE_STATE
;
317 Profile
* ManagePasswordsBubbleModel::GetProfile() const {
318 return GetProfileFromWebContents(web_contents());
321 bool ManagePasswordsBubbleModel::IsNewUIActive() const {
322 return base::CommandLine::ForCurrentProcess()->HasSwitch(
323 switches::kEnableCredentialManagerAPI
);
327 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
328 return GetFieldWidth(USERNAME_FIELD
);
332 int ManagePasswordsBubbleModel::PasswordFieldWidth() {
333 return GetFieldWidth(PASSWORD_FIELD
);
336 void ManagePasswordsBubbleModel::UpdatePendingStateTitle() {
337 title_brand_link_range_
= gfx::Range();
338 if (never_save_passwords_
) {
339 title_
= l10n_util::GetStringUTF16(
340 IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE
);
341 } else if (IsSmartLockBrandingEnabled(GetProfile())) {
342 // "Google Smart Lock" should be a hyperlink.
343 base::string16 brand_link
=
344 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK
);
346 title_
= l10n_util::GetStringFUTF16(IDS_SAVE_PASSWORD
, brand_link
, &offset
);
347 title_brand_link_range_
= gfx::Range(offset
, offset
+ brand_link
.length());
349 base::string16 brand_link
=
350 l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD_TITLE_BRAND
);
351 title_
= l10n_util::GetStringFUTF16(IDS_SAVE_PASSWORD
, brand_link
);