manage_passwords_bubble_model.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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.
4
5#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
6
7#include "base/strings/string_split.h"
8#include "base/strings/string_util.h"
9#include "chrome/browser/password_manager/password_store_factory.h"
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/browser_finder.h"
12#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
13#include "chrome/grit/generated_resources.h"
14#include "components/password_manager/core/browser/password_store.h"
15#include "components/password_manager/core/common/password_manager_ui.h"
16#include "ui/base/l10n/l10n_util.h"
17#include "ui/base/resource/resource_bundle.h"
18
19using autofill::PasswordFormMap;
20using content::WebContents;
21namespace metrics_util = password_manager::metrics_util;
22
23namespace {
24
25enum FieldType { USERNAME_FIELD, PASSWORD_FIELD };
26
27const int kUsernameFieldSize = 30;
28const int kPasswordFieldSize = 22;
29
30// Returns the width of |type| field.
31int GetFieldWidth(FieldType type) {
32  return ui::ResourceBundle::GetSharedInstance()
33      .GetFontList(ui::ResourceBundle::SmallFont)
34      .GetExpectedTextWidth(type == USERNAME_FIELD ? kUsernameFieldSize
35                                                   : kPasswordFieldSize);
36}
37
38void SetupLinkifiedText(const base::string16& string_with_separator,
39                        base::string16* text,
40                        gfx::Range* link_range) {
41  std::vector<base::string16> pieces;
42  base::SplitStringDontTrim(string_with_separator,
43                            '|',  // separator
44                            &pieces);
45  DCHECK_EQ(3u, pieces.size());
46  *link_range = gfx::Range(pieces[0].size(),
47                           pieces[0].size() + pieces[1].size());
48  *text = JoinString(pieces, base::string16());
49}
50
51}  // namespace
52
53ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
54    content::WebContents* web_contents)
55    : content::WebContentsObserver(web_contents),
56      display_disposition_(
57          metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING),
58      dismissal_reason_(metrics_util::NOT_DISPLAYED) {
59  ManagePasswordsUIController* controller =
60      ManagePasswordsUIController::FromWebContents(web_contents);
61
62  // TODO(mkwst): Reverse this logic. The controller should populate the model
63  // directly rather than the model pulling from the controller. Perhaps like
64  // `controller->PopulateModel(this)`.
65  state_ = controller->state();
66  if (password_manager::ui::IsPendingState(state_))
67    pending_credentials_ = controller->PendingCredentials();
68  best_matches_ = controller->best_matches();
69
70  if (password_manager::ui::IsPendingState(state_)) {
71    title_ = l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD);
72  } else if (state_ == password_manager::ui::BLACKLIST_STATE) {
73    title_ = l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED_TITLE);
74  } else if (state_ == password_manager::ui::CONFIRMATION_STATE) {
75    title_ =
76        l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TITLE);
77  } else {
78    title_ = l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_TITLE);
79  }
80
81  SetupLinkifiedText(
82      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT),
83      &save_confirmation_text_,
84      &save_confirmation_link_range_);
85
86  manage_link_ =
87      l10n_util::GetStringUTF16(IDS_OPTIONS_PASSWORDS_MANAGE_PASSWORDS_LINK);
88}
89
90ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {}
91
92void ManagePasswordsBubbleModel::OnBubbleShown(
93    ManagePasswordsBubble::DisplayReason reason) {
94  if (reason == ManagePasswordsBubble::USER_ACTION) {
95    if (password_manager::ui::IsPendingState(state_)) {
96      display_disposition_ = metrics_util::MANUAL_WITH_PASSWORD_PENDING;
97    } else if (state_ == password_manager::ui::BLACKLIST_STATE) {
98      display_disposition_ = metrics_util::MANUAL_BLACKLISTED;
99    } else {
100      display_disposition_ = metrics_util::MANUAL_MANAGE_PASSWORDS;
101    }
102  } else {
103    if (state_ == password_manager::ui::CONFIRMATION_STATE) {
104      display_disposition_ =
105          metrics_util::AUTOMATIC_GENERATED_PASSWORD_CONFIRMATION;
106    } else {
107      display_disposition_ = metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
108    }
109  }
110  metrics_util::LogUIDisplayDisposition(display_disposition_);
111
112  // Default to a dismissal reason of "no interaction". If the user interacts
113  // with the button in such a way that it closes, we'll reset this value
114  // accordingly.
115  dismissal_reason_ = metrics_util::NO_DIRECT_INTERACTION;
116}
117
118void ManagePasswordsBubbleModel::OnBubbleHidden() {
119  if (dismissal_reason_ == metrics_util::NOT_DISPLAYED)
120    return;
121
122  metrics_util::LogUIDismissalReason(dismissal_reason_);
123}
124
125void ManagePasswordsBubbleModel::OnNopeClicked() {
126  dismissal_reason_ = metrics_util::CLICKED_NOPE;
127  state_ = password_manager::ui::PENDING_PASSWORD_STATE;
128}
129
130void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
131  dismissal_reason_ = metrics_util::CLICKED_NEVER;
132  ManagePasswordsUIController* manage_passwords_ui_controller =
133      ManagePasswordsUIController::FromWebContents(web_contents());
134  manage_passwords_ui_controller->NeverSavePassword();
135  state_ = password_manager::ui::BLACKLIST_STATE;
136}
137
138void ManagePasswordsBubbleModel::OnUnblacklistClicked() {
139  dismissal_reason_ = metrics_util::CLICKED_UNBLACKLIST;
140  ManagePasswordsUIController* manage_passwords_ui_controller =
141      ManagePasswordsUIController::FromWebContents(web_contents());
142  manage_passwords_ui_controller->UnblacklistSite();
143  state_ = password_manager::ui::MANAGE_STATE;
144}
145
146void ManagePasswordsBubbleModel::OnSaveClicked() {
147  dismissal_reason_ = metrics_util::CLICKED_SAVE;
148  ManagePasswordsUIController* manage_passwords_ui_controller =
149      ManagePasswordsUIController::FromWebContents(web_contents());
150  manage_passwords_ui_controller->SavePassword();
151  state_ = password_manager::ui::MANAGE_STATE;
152}
153
154void ManagePasswordsBubbleModel::OnDoneClicked() {
155  dismissal_reason_ = metrics_util::CLICKED_DONE;
156}
157
158// TODO(gcasto): Is it worth having this be separate from OnDoneClicked()?
159// User intent is pretty similar in both cases.
160void ManagePasswordsBubbleModel::OnOKClicked() {
161  dismissal_reason_ = metrics_util::CLICKED_OK;
162}
163
164void ManagePasswordsBubbleModel::OnManageLinkClicked() {
165  dismissal_reason_ = metrics_util::CLICKED_MANAGE;
166  ManagePasswordsUIController::FromWebContents(web_contents())
167      ->NavigateToPasswordManagerSettingsPage();
168}
169
170void ManagePasswordsBubbleModel::OnPasswordAction(
171    const autofill::PasswordForm& password_form,
172    PasswordAction action) {
173  if (!web_contents())
174    return;
175  Profile* profile =
176      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
177  password_manager::PasswordStore* password_store =
178      PasswordStoreFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS)
179          .get();
180  DCHECK(password_store);
181  if (action == REMOVE_PASSWORD)
182    password_store->RemoveLogin(password_form);
183  else
184    password_store->AddLogin(password_form);
185}
186
187// static
188int ManagePasswordsBubbleModel::UsernameFieldWidth() {
189  return GetFieldWidth(USERNAME_FIELD);
190}
191
192// static
193int ManagePasswordsBubbleModel::PasswordFieldWidth() {
194  return GetFieldWidth(PASSWORD_FIELD);
195}
196