Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / passwords / manage_passwords_bubble_model.cc
bloba821a78efecc4245cced222f9142c4042ab31732
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;
38 namespace {
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) {
54 if (!web_contents)
55 return nullptr;
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);
71 });
72 return result.Pass();
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);
83 } // namespace
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];
108 } else {
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) {
116 title_ =
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.
122 } else {
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;
138 size_t offset;
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());
146 manage_link_ =
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) {
155 switch (state_) {
156 case password_manager::ui::PENDING_PASSWORD_STATE:
157 display_disposition_ = metrics_util::MANUAL_WITH_PASSWORD_PENDING;
158 break;
159 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
160 display_disposition_ =
161 metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE;
162 break;
163 case password_manager::ui::MANAGE_STATE:
164 display_disposition_ = metrics_util::MANUAL_MANAGE_PASSWORDS;
165 break;
166 default:
167 break;
169 } else {
170 switch (state_) {
171 case password_manager::ui::PENDING_PASSWORD_STATE:
172 display_disposition_ = metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
173 break;
174 case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
175 display_disposition_ =
176 metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE;
177 break;
178 case password_manager::ui::CONFIRMATION_STATE:
179 display_disposition_ =
180 metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION;
181 break;
182 case password_manager::ui::CREDENTIAL_REQUEST_STATE:
183 display_disposition_ = metrics_util::AUTOMATIC_CREDENTIAL_REQUEST;
184 break;
185 case password_manager::ui::AUTO_SIGNIN_STATE:
186 display_disposition_ = metrics_util::AUTOMATIC_SIGNIN_TOAST;
187 break;
188 default:
189 break;
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
196 // accordingly.
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 =
206 web_contents() ?
207 ManagePasswordsUIController::FromWebContents(web_contents())
208 : nullptr;
209 if (manage_passwords_ui_controller)
210 manage_passwords_ui_controller->OnBubbleHidden();
211 if (dismissal_reason_ == metrics_util::NOT_DISPLAYED)
212 return;
214 if (state_ != password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
215 // We have separate metrics for the Update bubble so do not record dismissal
216 // reason for it.
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();
284 } else {
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) {
310 if (!web_contents())
311 return;
312 Profile* profile =
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);
320 else
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,
331 credential_type);
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();
344 // static
345 int ManagePasswordsBubbleModel::UsernameFieldWidth() {
346 return GetFieldWidth(USERNAME_FIELD);
349 // static
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];