1 // Copyright 2014 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_ui_controller.h"
7 #include "chrome/app/chrome_command_ids.h"
8 #include "chrome/browser/browsing_data/browsing_data_helper.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/password_manager/password_store_factory.h"
11 #include "chrome/browser/ui/browser_command_controller.h"
12 #include "chrome/browser/ui/browser_dialogs.h"
13 #include "chrome/browser/ui/browser_finder.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/chrome_pages.h"
16 #include "chrome/browser/ui/location_bar/location_bar.h"
17 #include "chrome/browser/ui/passwords/manage_passwords_icon.h"
18 #include "chrome/common/url_constants.h"
19 #include "components/password_manager/core/browser/password_store.h"
20 #include "content/public/browser/notification_service.h"
22 using autofill::PasswordFormMap
;
23 using password_manager::PasswordFormManager
;
27 password_manager::PasswordStore
* GetPasswordStore(
28 content::WebContents
* web_contents
) {
29 return PasswordStoreFactory::GetForProfile(
30 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
31 Profile::EXPLICIT_ACCESS
).get();
34 autofill::ConstPasswordFormMap
ConstifyMap(
35 const autofill::PasswordFormMap
& map
) {
36 autofill::ConstPasswordFormMap ret
;
37 ret
.insert(map
.begin(), map
.end());
41 // Performs a deep copy of the PasswordForm pointers in |map|. The resulting map
42 // is returned via |ret|. |deleter| is populated with these new objects.
43 void DeepCopyMap(const autofill::PasswordFormMap
& map
,
44 autofill::ConstPasswordFormMap
* ret
,
45 ScopedVector
<autofill::PasswordForm
>* deleter
) {
46 ConstifyMap(map
).swap(*ret
);
48 for (autofill::ConstPasswordFormMap::iterator i
= ret
->begin();
49 i
!= ret
->end(); ++i
) {
50 deleter
->push_back(new autofill::PasswordForm(*i
->second
));
51 i
->second
= deleter
->back();
57 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController
);
59 ManagePasswordsUIController::ManagePasswordsUIController(
60 content::WebContents
* web_contents
)
61 : content::WebContentsObserver(web_contents
),
62 state_(password_manager::ui::INACTIVE_STATE
) {
63 password_manager::PasswordStore
* password_store
=
64 GetPasswordStore(web_contents
);
66 password_store
->AddObserver(this);
69 ManagePasswordsUIController::~ManagePasswordsUIController() {}
71 void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() {
72 // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't
73 // display either the bubble or the icon.
74 if (!BrowsingDataHelper::IsWebScheme(
75 web_contents()->GetLastCommittedURL().scheme())) {
76 state_
= password_manager::ui::INACTIVE_STATE
;
79 #if !defined(OS_ANDROID)
80 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
83 LocationBar
* location_bar
= browser
->window()->GetLocationBar();
85 location_bar
->UpdateManagePasswordsIconAndBubble();
89 void ManagePasswordsUIController::OnPasswordSubmitted(
90 scoped_ptr
<PasswordFormManager
> form_manager
) {
91 form_manager_
= form_manager
.Pass();
92 password_form_map_
= ConstifyMap(form_manager_
->best_matches());
93 origin_
= PendingCredentials().origin
;
94 state_
= password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
;
95 UpdateBubbleAndIconVisibility();
98 void ManagePasswordsUIController::OnAutomaticPasswordSave(
99 scoped_ptr
<PasswordFormManager
> form_manager
) {
100 form_manager_
= form_manager
.Pass();
101 password_form_map_
= ConstifyMap(form_manager_
->best_matches());
102 password_form_map_
[form_manager_
->associated_username()] =
103 &form_manager_
->pending_credentials();
104 origin_
= form_manager_
->pending_credentials().origin
;
105 state_
= password_manager::ui::CONFIRMATION_STATE
;
106 UpdateBubbleAndIconVisibility();
109 void ManagePasswordsUIController::OnPasswordAutofilled(
110 const PasswordFormMap
& password_form_map
) {
111 DeepCopyMap(password_form_map
, &password_form_map_
, &new_password_forms_
);
112 origin_
= password_form_map_
.begin()->second
->origin
;
113 state_
= password_manager::ui::MANAGE_STATE
;
114 UpdateBubbleAndIconVisibility();
117 void ManagePasswordsUIController::OnBlacklistBlockedAutofill(
118 const PasswordFormMap
& password_form_map
) {
119 DeepCopyMap(password_form_map
, &password_form_map_
, &new_password_forms_
);
120 origin_
= password_form_map_
.begin()->second
->origin
;
121 state_
= password_manager::ui::BLACKLIST_STATE
;
122 UpdateBubbleAndIconVisibility();
125 void ManagePasswordsUIController::WebContentsDestroyed() {
126 password_manager::PasswordStore
* password_store
=
127 GetPasswordStore(web_contents());
129 password_store
->RemoveObserver(this);
132 void ManagePasswordsUIController::OnLoginsChanged(
133 const password_manager::PasswordStoreChangeList
& changes
) {
134 password_manager::ui::State current_state
= state_
;
135 for (password_manager::PasswordStoreChangeList::const_iterator it
=
139 const autofill::PasswordForm
& changed_form
= it
->form();
140 if (changed_form
.origin
!= origin_
)
143 if (it
->type() == password_manager::PasswordStoreChange::REMOVE
) {
144 password_form_map_
.erase(changed_form
.username_value
);
145 if (changed_form
.blacklisted_by_user
)
146 state_
= password_manager::ui::MANAGE_STATE
;
148 new_password_forms_
.push_back(new autofill::PasswordForm(changed_form
));
149 password_form_map_
[changed_form
.username_value
] =
150 new_password_forms_
.back();
151 if (changed_form
.blacklisted_by_user
)
152 state_
= password_manager::ui::BLACKLIST_STATE
;
155 if (current_state
!= state_
)
156 UpdateBubbleAndIconVisibility();
159 void ManagePasswordsUIController::
160 NavigateToPasswordManagerSettingsPage() {
161 // TODO(mkwst): chrome_pages.h is compiled out of Android. Need to figure out
162 // how this navigation should work there.
163 #if !defined(OS_ANDROID)
164 chrome::ShowSettingsSubPage(
165 chrome::FindBrowserWithWebContents(web_contents()),
166 chrome::kPasswordManagerSubPage
);
170 void ManagePasswordsUIController::SavePassword() {
171 DCHECK(PasswordPendingUserDecision());
172 SavePasswordInternal();
173 state_
= password_manager::ui::MANAGE_STATE
;
174 UpdateBubbleAndIconVisibility();
177 void ManagePasswordsUIController::SavePasswordInternal() {
178 DCHECK(form_manager_
.get());
179 form_manager_
->Save();
182 void ManagePasswordsUIController::NeverSavePassword() {
183 DCHECK(PasswordPendingUserDecision());
184 NeverSavePasswordInternal();
185 state_
= password_manager::ui::BLACKLIST_STATE
;
186 UpdateBubbleAndIconVisibility();
189 void ManagePasswordsUIController::NeverSavePasswordInternal() {
190 DCHECK(form_manager_
.get());
191 form_manager_
->PermanentlyBlacklist();
194 void ManagePasswordsUIController::UnblacklistSite() {
195 // We're in one of two states: either the user _just_ blacklisted the site
196 // by clicking "Never save" in the pending bubble, or the user is visiting
197 // a blacklisted site.
199 // Either way, |password_form_map_| has been populated with the relevant
200 // form. We can safely pull it out, send it over to the password store
201 // for removal, and update our internal state.
202 DCHECK(!password_form_map_
.empty());
203 DCHECK(password_form_map_
.begin()->second
);
204 DCHECK(state_
== password_manager::ui::BLACKLIST_STATE
);
205 password_manager::PasswordStore
* password_store
=
206 GetPasswordStore(web_contents());
208 password_store
->RemoveLogin(*password_form_map_
.begin()->second
);
209 state_
= password_manager::ui::MANAGE_STATE
;
210 UpdateBubbleAndIconVisibility();
213 void ManagePasswordsUIController::DidNavigateMainFrame(
214 const content::LoadCommittedDetails
& details
,
215 const content::FrameNavigateParams
& params
) {
216 // Don't react to in-page (fragment) navigations.
217 if (details
.is_in_page
)
220 // Don't do anything if a navigation occurs before a user could reasonably
221 // interact with the password bubble.
222 if (timer_
&& timer_
->Elapsed() < base::TimeDelta::FromSeconds(1))
225 // Otherwise, reset the password manager and the timer.
226 state_
= password_manager::ui::INACTIVE_STATE
;
227 UpdateBubbleAndIconVisibility();
228 timer_
.reset(new base::ElapsedTimer());
231 void ManagePasswordsUIController::WasHidden() {
232 #if !defined(OS_ANDROID)
233 chrome::CloseManagePasswordsBubble(web_contents());
237 const autofill::PasswordForm
& ManagePasswordsUIController::
238 PendingCredentials() const {
239 DCHECK(form_manager_
);
240 return form_manager_
->pending_credentials();
243 void ManagePasswordsUIController::UpdateIconAndBubbleState(
244 ManagePasswordsIcon
* icon
) {
245 if (password_manager::ui::IsAutomaticDisplayState(state_
)) {
246 // We must display the icon before showing the bubble, as the bubble would
247 // be otherwise unanchored. However, we can't change the controller's state
248 // until _after_ the bubble is shown, as our metrics depend on the seeing
249 // the original state to determine if the bubble opened automagically or via
251 password_manager::ui::State end_state
=
252 GetEndStateForAutomaticState(state_
);
253 icon
->SetState(end_state
);
254 ShowBubbleWithoutUserInteraction();
257 icon
->SetState(state_
);
261 void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
262 DCHECK(password_manager::ui::IsAutomaticDisplayState(state_
));
263 #if !defined(OS_ANDROID)
264 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
265 if (!browser
|| browser
->toolbar_model()->input_in_progress())
267 CommandUpdater
* updater
= browser
->command_controller()->command_updater();
268 updater
->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE
);
272 bool ManagePasswordsUIController::PasswordPendingUserDecision() const {
273 return state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
274 state_
== password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
;