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_finder.h"
13 #include "chrome/browser/ui/browser_window.h"
14 #include "chrome/browser/ui/chrome_pages.h"
15 #include "chrome/browser/ui/location_bar/location_bar.h"
16 #include "chrome/browser/ui/passwords/manage_passwords_icon.h"
17 #include "chrome/common/url_constants.h"
18 #include "components/password_manager/core/browser/password_store.h"
19 #include "content/public/browser/notification_service.h"
21 using autofill::PasswordFormMap
;
22 using password_manager::PasswordFormManager
;
26 password_manager::PasswordStore
* GetPasswordStore(
27 content::WebContents
* web_contents
) {
28 return PasswordStoreFactory::GetForProfile(
29 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
30 Profile::EXPLICIT_ACCESS
).get();
33 autofill::ConstPasswordFormMap
ConstifyMap(
34 const autofill::PasswordFormMap
& map
) {
35 autofill::ConstPasswordFormMap ret
;
36 ret
.insert(map
.begin(), map
.end());
40 // Performs a deep copy of the PasswordForm pointers in |map|. The resulting map
41 // is returned via |ret|. |deleter| is populated with these new objects.
42 void DeepCopyMap(const autofill::PasswordFormMap
& map
,
43 autofill::ConstPasswordFormMap
* ret
,
44 ScopedVector
<autofill::PasswordForm
>* deleter
) {
45 ConstifyMap(map
).swap(*ret
);
47 for (autofill::ConstPasswordFormMap::iterator i
= ret
->begin();
48 i
!= ret
->end(); ++i
) {
49 deleter
->push_back(new autofill::PasswordForm(*i
->second
));
50 i
->second
= deleter
->back();
56 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController
);
58 ManagePasswordsUIController::ManagePasswordsUIController(
59 content::WebContents
* web_contents
)
60 : content::WebContentsObserver(web_contents
),
61 state_(password_manager::ui::INACTIVE_STATE
) {
62 password_manager::PasswordStore
* password_store
=
63 GetPasswordStore(web_contents
);
65 password_store
->AddObserver(this);
68 ManagePasswordsUIController::~ManagePasswordsUIController() {}
70 void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() {
71 // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't
72 // display either the bubble or the icon.
73 if (!BrowsingDataHelper::IsWebScheme(
74 web_contents()->GetLastCommittedURL().scheme())) {
75 state_
= password_manager::ui::INACTIVE_STATE
;
78 #if !defined(OS_ANDROID)
79 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
82 LocationBar
* location_bar
= browser
->window()->GetLocationBar();
84 location_bar
->UpdateManagePasswordsIconAndBubble();
88 void ManagePasswordsUIController::OnPasswordSubmitted(
89 scoped_ptr
<PasswordFormManager
> form_manager
) {
90 form_manager_
= form_manager
.Pass();
91 password_form_map_
= ConstifyMap(form_manager_
->best_matches());
92 origin_
= PendingCredentials().origin
;
93 state_
= password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
;
94 UpdateBubbleAndIconVisibility();
97 void ManagePasswordsUIController::OnAutomaticPasswordSave(
98 scoped_ptr
<PasswordFormManager
> form_manager
) {
99 form_manager_
= form_manager
.Pass();
100 password_form_map_
= ConstifyMap(form_manager_
->best_matches());
101 password_form_map_
[form_manager_
->associated_username()] =
102 &form_manager_
->pending_credentials();
103 origin_
= form_manager_
->pending_credentials().origin
;
104 state_
= password_manager::ui::CONFIRMATION_STATE
;
105 UpdateBubbleAndIconVisibility();
108 void ManagePasswordsUIController::OnPasswordAutofilled(
109 const PasswordFormMap
& password_form_map
) {
110 DeepCopyMap(password_form_map
, &password_form_map_
, &new_password_forms_
);
111 origin_
= password_form_map_
.begin()->second
->origin
;
112 state_
= password_manager::ui::MANAGE_STATE
;
113 UpdateBubbleAndIconVisibility();
116 void ManagePasswordsUIController::OnBlacklistBlockedAutofill(
117 const PasswordFormMap
& password_form_map
) {
118 DeepCopyMap(password_form_map
, &password_form_map_
, &new_password_forms_
);
119 origin_
= password_form_map_
.begin()->second
->origin
;
120 state_
= password_manager::ui::BLACKLIST_STATE
;
121 UpdateBubbleAndIconVisibility();
124 void ManagePasswordsUIController::WebContentsDestroyed() {
125 password_manager::PasswordStore
* password_store
=
126 GetPasswordStore(web_contents());
128 password_store
->RemoveObserver(this);
131 void ManagePasswordsUIController::OnLoginsChanged(
132 const password_manager::PasswordStoreChangeList
& changes
) {
133 password_manager::ui::State current_state
= state_
;
134 for (password_manager::PasswordStoreChangeList::const_iterator it
=
138 const autofill::PasswordForm
& changed_form
= it
->form();
139 if (changed_form
.origin
!= origin_
)
142 if (it
->type() == password_manager::PasswordStoreChange::REMOVE
) {
143 password_form_map_
.erase(changed_form
.username_value
);
144 if (changed_form
.blacklisted_by_user
)
145 state_
= password_manager::ui::MANAGE_STATE
;
147 new_password_forms_
.push_back(new autofill::PasswordForm(changed_form
));
148 password_form_map_
[changed_form
.username_value
] =
149 new_password_forms_
.back();
150 if (changed_form
.blacklisted_by_user
)
151 state_
= password_manager::ui::BLACKLIST_STATE
;
154 if (current_state
!= state_
)
155 UpdateBubbleAndIconVisibility();
158 void ManagePasswordsUIController::
159 NavigateToPasswordManagerSettingsPage() {
160 // TODO(mkwst): chrome_pages.h is compiled out of Android. Need to figure out
161 // how this navigation should work there.
162 #if !defined(OS_ANDROID)
163 chrome::ShowSettingsSubPage(
164 chrome::FindBrowserWithWebContents(web_contents()),
165 chrome::kPasswordManagerSubPage
);
169 void ManagePasswordsUIController::NavigateToAccountCentralManagementPage() {
170 // TODO(gcasto): FindBowserWithWebContents() doesn't exist on Android.
171 // Need to determine how this should work there.
172 #if !defined(OS_ANDROID)
173 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
174 content::OpenURLParams
params(
175 GURL(chrome::kAutoPasswordGenerationLearnMoreURL
), content::Referrer(),
176 NEW_FOREGROUND_TAB
, content::PAGE_TRANSITION_LINK
, false);
177 browser
->OpenURL(params
);
181 void ManagePasswordsUIController::SavePassword() {
182 DCHECK(PasswordPendingUserDecision());
183 SavePasswordInternal();
184 state_
= password_manager::ui::MANAGE_STATE
;
185 UpdateBubbleAndIconVisibility();
188 void ManagePasswordsUIController::SavePasswordInternal() {
189 DCHECK(form_manager_
.get());
190 form_manager_
->Save();
193 void ManagePasswordsUIController::NeverSavePassword() {
194 DCHECK(PasswordPendingUserDecision());
195 NeverSavePasswordInternal();
196 state_
= password_manager::ui::BLACKLIST_STATE
;
197 UpdateBubbleAndIconVisibility();
200 void ManagePasswordsUIController::NeverSavePasswordInternal() {
201 DCHECK(form_manager_
.get());
202 form_manager_
->PermanentlyBlacklist();
205 void ManagePasswordsUIController::UnblacklistSite() {
206 // We're in one of two states: either the user _just_ blacklisted the site
207 // by clicking "Never save" in the pending bubble, or the user is visiting
208 // a blacklisted site.
210 // Either way, |password_form_map_| has been populated with the relevant
211 // form. We can safely pull it out, send it over to the password store
212 // for removal, and update our internal state.
213 DCHECK(!password_form_map_
.empty());
214 DCHECK(password_form_map_
.begin()->second
);
215 DCHECK(state_
== password_manager::ui::BLACKLIST_STATE
);
216 password_manager::PasswordStore
* password_store
=
217 GetPasswordStore(web_contents());
219 password_store
->RemoveLogin(*password_form_map_
.begin()->second
);
220 state_
= password_manager::ui::MANAGE_STATE
;
221 UpdateBubbleAndIconVisibility();
224 void ManagePasswordsUIController::DidNavigateMainFrame(
225 const content::LoadCommittedDetails
& details
,
226 const content::FrameNavigateParams
& params
) {
227 // Don't react to in-page (fragment) navigations.
228 if (details
.is_in_page
)
231 // Don't do anything if a navigation occurs before a user could reasonably
232 // interact with the password bubble.
233 if (timer_
&& timer_
->Elapsed() < base::TimeDelta::FromSeconds(1))
236 // Otherwise, reset the password manager and the timer.
237 state_
= password_manager::ui::INACTIVE_STATE
;
238 UpdateBubbleAndIconVisibility();
239 timer_
.reset(new base::ElapsedTimer());
242 const autofill::PasswordForm
& ManagePasswordsUIController::
243 PendingCredentials() const {
244 DCHECK(form_manager_
);
245 return form_manager_
->pending_credentials();
248 void ManagePasswordsUIController::UpdateIconAndBubbleState(
249 ManagePasswordsIcon
* icon
) {
250 if (password_manager::ui::IsAutomaticDisplayState(state_
)) {
251 // We must display the icon before showing the bubble, as the bubble would
252 // be otherwise unanchored. However, we can't change the controller's state
253 // until _after_ the bubble is shown, as our metrics depend on the seeing
254 // the original state to determine if the bubble opened automagically or via
256 password_manager::ui::State end_state
=
257 GetEndStateForAutomaticState(state_
);
258 icon
->SetState(end_state
);
259 ShowBubbleWithoutUserInteraction();
262 icon
->SetState(state_
);
266 void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
267 DCHECK(password_manager::ui::IsAutomaticDisplayState(state_
));
268 #if !defined(OS_ANDROID)
269 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
270 if (!browser
|| browser
->toolbar_model()->input_in_progress())
272 CommandUpdater
* updater
= browser
->command_controller()->command_updater();
273 updater
->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE
);
277 bool ManagePasswordsUIController::PasswordPendingUserDecision() const {
278 return state_
== password_manager::ui::PENDING_PASSWORD_STATE
||
279 state_
== password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
;