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 "base/auto_reset.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #include "chrome/browser/browsing_data/browsing_data_helper.h"
10 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
11 #include "chrome/browser/password_manager/password_store_factory.h"
12 #include "chrome/browser/ui/browser_command_controller.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/browser/ui/tab_dialogs.h"
19 #include "chrome/common/url_constants.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
22 #include "components/password_manager/core/browser/password_bubble_experiment.h"
23 #include "components/password_manager/core/browser/password_form_manager.h"
24 #include "components/password_manager/core/common/credential_manager_types.h"
25 #include "content/public/browser/navigation_details.h"
26 #include "ui/base/l10n/l10n_util.h"
28 #if defined(OS_ANDROID)
29 #include "chrome/browser/android/chrome_application.h"
30 #include "chrome/browser/infobars/infobar_service.h"
31 #include "chrome/browser/password_manager/account_chooser_infobar_delegate_android.h"
34 using autofill::PasswordFormMap
;
35 using password_manager::PasswordFormManager
;
39 // Minimal time span the bubble should survive implicit navigations.
40 const int kBubbleMinTime
= 5;
42 password_manager::PasswordStore
* GetPasswordStore(
43 content::WebContents
* web_contents
) {
44 return PasswordStoreFactory::GetForProfile(
45 Profile::FromBrowserContext(web_contents
->GetBrowserContext()),
46 ServiceAccessType::EXPLICIT_ACCESS
).get();
51 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController
);
53 ManagePasswordsUIController::ManagePasswordsUIController(
54 content::WebContents
* web_contents
)
55 : content::WebContentsObserver(web_contents
),
56 should_pop_up_bubble_(false) {
57 passwords_data_
.set_client(
58 ChromePasswordManagerClient::FromWebContents(web_contents
));
59 password_manager::PasswordStore
* password_store
=
60 GetPasswordStore(web_contents
);
62 password_store
->AddObserver(this);
65 ManagePasswordsUIController::~ManagePasswordsUIController() {}
67 void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() {
68 // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't
69 // display either the bubble or the icon.
70 if (!BrowsingDataHelper::IsWebScheme(
71 web_contents()->GetLastCommittedURL().scheme())) {
72 passwords_data_
.OnInactive();
75 #if !defined(OS_ANDROID)
76 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
79 LocationBar
* location_bar
= browser
->window()->GetLocationBar();
81 location_bar
->UpdateManagePasswordsIconAndBubble();
85 void ManagePasswordsUIController::
86 UpdateAndroidAccountChooserInfoBarVisibility() {
87 #if defined(OS_ANDROID)
88 AccountChooserInfoBarDelegateAndroid::Create(web_contents(), this);
89 should_pop_up_bubble_
= false;
93 base::TimeDelta
ManagePasswordsUIController::Elapsed() const {
94 return timer_
? timer_
->Elapsed() : base::TimeDelta::Max();
97 void ManagePasswordsUIController::OnPasswordSubmitted(
98 scoped_ptr
<PasswordFormManager
> form_manager
) {
99 bool blacklisted
= form_manager
->IsBlacklisted();
100 passwords_data_
.OnPendingPassword(form_manager
.Pass());
101 timer_
.reset(new base::ElapsedTimer
);
102 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, !blacklisted
);
103 UpdateBubbleAndIconVisibility();
106 void ManagePasswordsUIController::OnUpdatePasswordSubmitted(
107 scoped_ptr
<PasswordFormManager
> form_manager
) {
108 passwords_data_
.OnUpdatePassword(form_manager
.Pass());
109 timer_
.reset(new base::ElapsedTimer
);
110 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, true);
111 UpdateBubbleAndIconVisibility();
114 bool ManagePasswordsUIController::OnChooseCredentials(
115 ScopedVector
<autofill::PasswordForm
> local_credentials
,
116 ScopedVector
<autofill::PasswordForm
> federated_credentials
,
118 base::Callback
<void(const password_manager::CredentialInfo
&)> callback
) {
119 DCHECK_IMPLIES(local_credentials
.empty(), !federated_credentials
.empty());
120 passwords_data_
.OnRequestCredentials(local_credentials
.Pass(),
121 federated_credentials
.Pass(),
123 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, true);
124 #if defined(OS_ANDROID)
125 UpdateAndroidAccountChooserInfoBarVisibility();
127 UpdateBubbleAndIconVisibility();
129 if (!should_pop_up_bubble_
) {
130 passwords_data_
.set_credentials_callback(callback
);
133 passwords_data_
.TransitionToState(password_manager::ui::MANAGE_STATE
);
137 void ManagePasswordsUIController::OnAutoSignin(
138 ScopedVector
<autofill::PasswordForm
> local_forms
) {
139 DCHECK(!local_forms
.empty());
140 passwords_data_
.OnAutoSignin(local_forms
.Pass());
141 timer_
.reset(new base::ElapsedTimer
);
142 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, true);
143 UpdateBubbleAndIconVisibility();
146 void ManagePasswordsUIController::OnAutomaticPasswordSave(
147 scoped_ptr
<PasswordFormManager
> form_manager
) {
148 passwords_data_
.OnAutomaticPasswordSave(form_manager
.Pass());
149 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, true);
150 UpdateBubbleAndIconVisibility();
153 void ManagePasswordsUIController::OnPasswordAutofilled(
154 const PasswordFormMap
& password_form_map
) {
155 passwords_data_
.OnPasswordAutofilled(password_form_map
);
156 UpdateBubbleAndIconVisibility();
159 void ManagePasswordsUIController::OnBlacklistBlockedAutofill(
160 const PasswordFormMap
& password_form_map
) {
161 passwords_data_
.OnInactive();
162 UpdateBubbleAndIconVisibility();
165 void ManagePasswordsUIController::OnLoginsChanged(
166 const password_manager::PasswordStoreChangeList
& changes
) {
167 password_manager::ui::State current_state
= state();
168 passwords_data_
.ProcessLoginsChanged(changes
);
169 if (current_state
!= state())
170 UpdateBubbleAndIconVisibility();
173 void ManagePasswordsUIController::NavigateToPasswordManagerSettingsPage() {
174 #if defined(OS_ANDROID)
175 chrome::android::ChromeApplication::ShowPasswordSettings();
177 chrome::ShowSettingsSubPage(
178 chrome::FindBrowserWithWebContents(web_contents()),
179 chrome::kPasswordManagerSubPage
);
183 void ManagePasswordsUIController::NavigateToExternalPasswordManager() {
184 #if defined(OS_ANDROID)
187 chrome::NavigateParams
params(
188 chrome::FindBrowserWithWebContents(web_contents()),
189 GURL(chrome::kPasswordManagerAccountDashboardURL
),
190 ui::PAGE_TRANSITION_LINK
);
191 params
.disposition
= NEW_FOREGROUND_TAB
;
192 chrome::Navigate(¶ms
);
196 void ManagePasswordsUIController::NavigateToSmartLockPage() {
197 #if defined(OS_ANDROID)
200 chrome::NavigateParams
params(
201 chrome::FindBrowserWithWebContents(web_contents()),
202 GURL(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SMART_LOCK_PAGE
)),
203 ui::PAGE_TRANSITION_LINK
);
204 params
.disposition
= NEW_FOREGROUND_TAB
;
205 chrome::Navigate(¶ms
);
209 void ManagePasswordsUIController::SavePassword() {
210 DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE
, state());
211 SavePasswordInternal();
212 passwords_data_
.TransitionToState(password_manager::ui::MANAGE_STATE
);
213 UpdateBubbleAndIconVisibility();
216 void ManagePasswordsUIController::UpdatePassword(
217 const autofill::PasswordForm
& password_form
) {
218 DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
, state());
219 UpdatePasswordInternal(password_form
);
220 passwords_data_
.TransitionToState(password_manager::ui::MANAGE_STATE
);
221 UpdateBubbleAndIconVisibility();
224 void ManagePasswordsUIController::ChooseCredential(
225 const autofill::PasswordForm
& form
,
226 password_manager::CredentialType credential_type
) {
227 DCHECK_EQ(password_manager::ui::CREDENTIAL_REQUEST_STATE
, state());
228 DCHECK(!passwords_data_
.credentials_callback().is_null());
230 // Here, |credential_type| refers to whether the credential was originally
231 // passed into ::OnChooseCredentials as part of the |local_credentials| or
232 // |federated_credentials| lists (e.g. whether it is an existing credential
233 // saved for this origin, or whether we should synthesize a new
234 // FederatedCredential).
236 // If |credential_type| is federated, the credential MUST be returned as
237 // a FederatedCredential in order to prevent password information leaking
240 // If |credential_type| is local, the credential MIGHT be a PasswordCredential
241 // or it MIGHT be a FederatedCredential. We inspect the |federation_url|
242 // field to determine which we should return.
244 // TODO(mkwst): Clean this up. It is confusing.
245 password_manager::CredentialType type_to_return
;
246 if (credential_type
==
247 password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD
&&
248 form
.federation_url
.is_empty()) {
249 type_to_return
= password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD
;
250 } else if (credential_type
==
251 password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY
) {
252 type_to_return
= password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY
;
255 password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED
;
257 password_manager::CredentialInfo info
=
258 password_manager::CredentialInfo(form
, type_to_return
);
259 passwords_data_
.credentials_callback().Run(info
);
260 passwords_data_
.set_credentials_callback(
261 ManagePasswordsState::CredentialsCallback());
264 void ManagePasswordsUIController::SavePasswordInternal() {
265 password_manager::PasswordStore
* password_store
=
266 GetPasswordStore(web_contents());
267 password_manager::PasswordFormManager
* form_manager
=
268 passwords_data_
.form_manager();
269 for (const autofill::PasswordForm
* form
:
270 form_manager
->blacklisted_matches()) {
271 password_store
->RemoveLogin(*form
);
274 form_manager
->Save();
277 void ManagePasswordsUIController::UpdatePasswordInternal(
278 const autofill::PasswordForm
& password_form
) {
279 password_manager::PasswordFormManager
* form_manager
=
280 passwords_data_
.form_manager();
281 form_manager
->Update(password_form
);
284 void ManagePasswordsUIController::NeverSavePassword() {
285 DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE
, state());
286 NeverSavePasswordInternal();
287 // The state stays the same.
290 void ManagePasswordsUIController::NeverSavePasswordInternal() {
291 password_manager::PasswordFormManager
* form_manager
=
292 passwords_data_
.form_manager();
293 DCHECK(form_manager
);
294 form_manager
->PermanentlyBlacklist();
297 void ManagePasswordsUIController::ManageAccounts() {
298 DCHECK_EQ(password_manager::ui::AUTO_SIGNIN_STATE
, state());
299 passwords_data_
.TransitionToState(password_manager::ui::MANAGE_STATE
);
300 base::AutoReset
<bool> resetter(&should_pop_up_bubble_
, true);
301 UpdateBubbleAndIconVisibility();
304 void ManagePasswordsUIController::DidNavigateMainFrame(
305 const content::LoadCommittedDetails
& details
,
306 const content::FrameNavigateParams
& params
) {
307 // Don't react to in-page (fragment) navigations.
308 if (details
.is_in_page
)
311 // Don't do anything if a navigation occurs before a user could reasonably
312 // interact with the password bubble.
313 if (Elapsed() < base::TimeDelta::FromSeconds(kBubbleMinTime
))
316 // Otherwise, reset the password manager and the timer.
317 passwords_data_
.OnInactive();
318 UpdateBubbleAndIconVisibility();
319 // This allows the bubble to survive several redirects in case the whole
320 // process of navigating to the landing page is longer than 1 second.
321 timer_
.reset(new base::ElapsedTimer());
324 void ManagePasswordsUIController::WasHidden() {
325 #if !defined(OS_ANDROID)
326 TabDialogs::FromWebContents(web_contents())->HideManagePasswordsBubble();
330 const autofill::PasswordForm
& ManagePasswordsUIController::
331 PendingPassword() const {
332 DCHECK(state() == password_manager::ui::PENDING_PASSWORD_STATE
||
333 state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
||
334 state() == password_manager::ui::CONFIRMATION_STATE
)
336 password_manager::PasswordFormManager
* form_manager
=
337 passwords_data_
.form_manager();
338 DCHECK(form_manager
);
339 return form_manager
->pending_credentials();
342 bool ManagePasswordsUIController::PasswordOverridden() const {
343 const password_manager::PasswordFormManager
* form_manager
=
344 passwords_data_
.form_manager();
345 return form_manager
? form_manager
->password_overridden() : false;
348 void ManagePasswordsUIController::UpdateIconAndBubbleState(
349 ManagePasswordsIcon
* icon
) {
350 if (should_pop_up_bubble_
) {
351 // We must display the icon before showing the bubble, as the bubble would
352 // be otherwise unanchored.
353 icon
->SetState(state());
354 ShowBubbleWithoutUserInteraction();
356 icon
->SetState(state());
360 void ManagePasswordsUIController::OnBubbleShown() {
361 should_pop_up_bubble_
= false;
364 void ManagePasswordsUIController::OnBubbleHidden() {
365 if (state() == password_manager::ui::CREDENTIAL_REQUEST_STATE
||
366 state() == password_manager::ui::CONFIRMATION_STATE
||
367 state() == password_manager::ui::AUTO_SIGNIN_STATE
) {
368 passwords_data_
.TransitionToState(password_manager::ui::MANAGE_STATE
);
369 UpdateBubbleAndIconVisibility();
373 void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() {
374 DCHECK(should_pop_up_bubble_
);
375 #if !defined(OS_ANDROID)
376 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents());
377 if (!browser
|| browser
->toolbar_model()->input_in_progress())
380 CommandUpdater
* updater
= browser
->command_controller()->command_updater();
381 updater
->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE
);
385 bool ManagePasswordsUIController::ShouldShowMultipleAccountUpdateUI() const {
386 return state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
&&
387 GetCurrentForms().size() > 1 && !PasswordOverridden();
390 void ManagePasswordsUIController::WebContentsDestroyed() {
391 password_manager::PasswordStore
* password_store
=
392 GetPasswordStore(web_contents());
394 password_store
->RemoveObserver(this);