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. 4 5 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h" 6 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/omnibox/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" 20 21 using autofill::PasswordFormMap; 22 using password_manager::PasswordFormManager; 23 24 namespace { 25 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(); 31 } 32 33 } // namespace 34 35 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController); 36 37 ManagePasswordsUIController::ManagePasswordsUIController( 38 content::WebContents* web_contents) 39 : content::WebContentsObserver(web_contents), 40 state_(password_manager::ui::INACTIVE_STATE) { 41 password_manager::PasswordStore* password_store = 42 GetPasswordStore(web_contents); 43 if (password_store) 44 password_store->AddObserver(this); 45 } 46 47 ManagePasswordsUIController::~ManagePasswordsUIController() {} 48 49 void ManagePasswordsUIController::UpdateBubbleAndIconVisibility() { 50 // If we're not on a "webby" URL (e.g. "chrome://sign-in"), we shouldn't 51 // display either the bubble or the icon. 52 if (!BrowsingDataHelper::IsWebScheme( 53 web_contents()->GetLastCommittedURL().scheme())) { 54 state_ = password_manager::ui::INACTIVE_STATE; 55 } 56 57 #if !defined(OS_ANDROID) 58 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 59 if (!browser) 60 return; 61 LocationBar* location_bar = browser->window()->GetLocationBar(); 62 DCHECK(location_bar); 63 location_bar->UpdateManagePasswordsIconAndBubble(); 64 #endif 65 } 66 67 void ManagePasswordsUIController::OnPasswordSubmitted( 68 PasswordFormManager* form_manager) { 69 form_manager_.reset(form_manager); 70 password_form_map_ = form_manager_->best_matches(); 71 origin_ = PendingCredentials().origin; 72 state_ = password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE; 73 UpdateBubbleAndIconVisibility(); 74 } 75 76 void ManagePasswordsUIController::OnPasswordAutofilled( 77 const PasswordFormMap& password_form_map) { 78 password_form_map_ = password_form_map; 79 origin_ = password_form_map_.begin()->second->origin; 80 state_ = password_manager::ui::MANAGE_STATE; 81 UpdateBubbleAndIconVisibility(); 82 } 83 84 void ManagePasswordsUIController::OnBlacklistBlockedAutofill( 85 const PasswordFormMap& password_form_map) { 86 password_form_map_ = password_form_map; 87 origin_ = password_form_map_.begin()->second->origin; 88 state_ = password_manager::ui::BLACKLIST_STATE; 89 UpdateBubbleAndIconVisibility(); 90 } 91 92 void ManagePasswordsUIController::WebContentsDestroyed() { 93 password_manager::PasswordStore* password_store = 94 GetPasswordStore(web_contents()); 95 if (password_store) 96 password_store->RemoveObserver(this); 97 } 98 99 void ManagePasswordsUIController::OnLoginsChanged( 100 const password_manager::PasswordStoreChangeList& changes) { 101 password_manager::ui::State current_state = state_; 102 for (password_manager::PasswordStoreChangeList::const_iterator it = 103 changes.begin(); 104 it != changes.end(); 105 it++) { 106 const autofill::PasswordForm& changed_form = it->form(); 107 if (changed_form.origin != origin_) 108 continue; 109 110 if (it->type() == password_manager::PasswordStoreChange::REMOVE) { 111 password_form_map_.erase(changed_form.username_value); 112 if (changed_form.blacklisted_by_user) 113 state_ = password_manager::ui::MANAGE_STATE; 114 } else { 115 new_password_forms_.push_back(new autofill::PasswordForm(changed_form)); 116 password_form_map_[changed_form.username_value] = 117 new_password_forms_.back(); 118 if (changed_form.blacklisted_by_user) 119 state_ = password_manager::ui::BLACKLIST_STATE; 120 } 121 } 122 if (current_state != state_) 123 UpdateBubbleAndIconVisibility(); 124 } 125 126 void ManagePasswordsUIController:: 127 NavigateToPasswordManagerSettingsPage() { 128 // TODO(mkwst): chrome_pages.h is compiled out of Android. Need to figure out 129 // how this navigation should work there. 130 #if !defined(OS_ANDROID) 131 chrome::ShowSettingsSubPage( 132 chrome::FindBrowserWithWebContents(web_contents()), 133 chrome::kPasswordManagerSubPage); 134 #endif 135 } 136 137 void ManagePasswordsUIController::SavePassword() { 138 DCHECK(PasswordPendingUserDecision()); 139 DCHECK(form_manager_.get()); 140 form_manager_->Save(); 141 state_ = password_manager::ui::MANAGE_STATE; 142 } 143 144 void ManagePasswordsUIController::NeverSavePassword() { 145 DCHECK(PasswordPendingUserDecision()); 146 DCHECK(form_manager_.get()); 147 form_manager_->PermanentlyBlacklist(); 148 state_ = password_manager::ui::BLACKLIST_STATE; 149 UpdateBubbleAndIconVisibility(); 150 } 151 152 void ManagePasswordsUIController::UnblacklistSite() { 153 // We're in one of two states: either the user _just_ blacklisted the site 154 // by clicking "Never save" in the pending bubble, or the user is visiting 155 // a blacklisted site. 156 // 157 // Either way, |password_form_map_| has been populated with the relevant 158 // form. We can safely pull it out, send it over to the password store 159 // for removal, and update our internal state. 160 DCHECK(!password_form_map_.empty()); 161 DCHECK(password_form_map_.begin()->second); 162 DCHECK(state_ == password_manager::ui::BLACKLIST_STATE); 163 password_manager::PasswordStore* password_store = 164 GetPasswordStore(web_contents()); 165 if (password_store) 166 password_store->RemoveLogin(*password_form_map_.begin()->second); 167 state_ = password_manager::ui::MANAGE_STATE; 168 UpdateBubbleAndIconVisibility(); 169 } 170 171 void ManagePasswordsUIController::DidNavigateMainFrame( 172 const content::LoadCommittedDetails& details, 173 const content::FrameNavigateParams& params) { 174 // Don't react to in-page (fragment) navigations. 175 if (details.is_in_page) 176 return; 177 178 // Don't do anything if a navigation occurs before a user could reasonably 179 // interact with the password bubble. 180 if (timer_ && timer_->Elapsed() < base::TimeDelta::FromSeconds(1)) 181 return; 182 183 // Otherwise, reset the password manager and the timer. 184 state_ = password_manager::ui::INACTIVE_STATE; 185 UpdateBubbleAndIconVisibility(); 186 timer_.reset(new base::ElapsedTimer()); 187 } 188 189 const autofill::PasswordForm& ManagePasswordsUIController:: 190 PendingCredentials() const { 191 DCHECK(form_manager_); 192 return form_manager_->pending_credentials(); 193 } 194 195 void ManagePasswordsUIController::UpdateIconAndBubbleState( 196 ManagePasswordsIcon* icon) { 197 if (state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE) { 198 // We must display the icon before showing the bubble, as the bubble would 199 // be otherwise unanchored. However, we can't change the controller's state 200 // until _after_ the bubble is shown, as our metrics depend on the 201 // distinction between PENDING_PASSWORD_AND_BUBBLE_STATE and 202 // PENDING_PASSWORD_STATE to determine if the bubble opened automagically 203 // or via user action. 204 icon->SetState(password_manager::ui::PENDING_PASSWORD_STATE); 205 ShowBubbleWithoutUserInteraction(); 206 state_ = password_manager::ui::PENDING_PASSWORD_STATE; 207 } else { 208 icon->SetState(state_); 209 } 210 } 211 212 void ManagePasswordsUIController::ShowBubbleWithoutUserInteraction() { 213 DCHECK_EQ(state_, password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE); 214 #if !defined(OS_ANDROID) 215 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 216 if (!browser || browser->toolbar_model()->input_in_progress()) 217 return; 218 CommandUpdater* updater = browser->command_controller()->command_updater(); 219 updater->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE); 220 #endif 221 } 222 223 bool ManagePasswordsUIController::PasswordPendingUserDecision() const { 224 return state_ == password_manager::ui::PENDING_PASSWORD_STATE || 225 state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE; 226 } 227