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_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" 21 22 using autofill::PasswordFormMap; 23 using password_manager::PasswordFormManager; 24 25 namespace { 26 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(); 32 } 33 34 autofill::ConstPasswordFormMap ConstifyMap( 35 const autofill::PasswordFormMap& map) { 36 autofill::ConstPasswordFormMap ret; 37 ret.insert(map.begin(), map.end()); 38 return ret; 39 } 40 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); 47 deleter->clear(); 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(); 52 } 53 } 54 55 } // namespace 56 57 DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController); 58 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); 65 if (password_store) 66 password_store->AddObserver(this); 67 } 68 69 ManagePasswordsUIController::~ManagePasswordsUIController() {} 70 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; 77 } 78 79 #if !defined(OS_ANDROID) 80 Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); 81 if (!browser) 82 return; 83 LocationBar* location_bar = browser->window()->GetLocationBar(); 84 DCHECK(location_bar); 85 location_bar->UpdateManagePasswordsIconAndBubble(); 86 #endif 87 } 88 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(); 96 } 97 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(); 107 } 108 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(); 115 } 116 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(); 123 } 124 125 void ManagePasswordsUIController::WebContentsDestroyed() { 126 password_manager::PasswordStore* password_store = 127 GetPasswordStore(web_contents()); 128 if (password_store) 129 password_store->RemoveObserver(this); 130 } 131 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 = 136 changes.begin(); 137 it != changes.end(); 138 it++) { 139 const autofill::PasswordForm& changed_form = it->form(); 140 if (changed_form.origin != origin_) 141 continue; 142 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; 147 } else { 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; 153 } 154 } 155 if (current_state != state_) 156 UpdateBubbleAndIconVisibility(); 157 } 158 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); 167 #endif 168 } 169 170 void ManagePasswordsUIController::SavePassword() { 171 DCHECK(PasswordPendingUserDecision()); 172 SavePasswordInternal(); 173 state_ = password_manager::ui::MANAGE_STATE; 174 UpdateBubbleAndIconVisibility(); 175 } 176 177 void ManagePasswordsUIController::SavePasswordInternal() { 178 DCHECK(form_manager_.get()); 179 form_manager_->Save(); 180 } 181 182 void ManagePasswordsUIController::NeverSavePassword() { 183 DCHECK(PasswordPendingUserDecision()); 184 NeverSavePasswordInternal(); 185 state_ = password_manager::ui::BLACKLIST_STATE; 186 UpdateBubbleAndIconVisibility(); 187 } 188 189 void ManagePasswordsUIController::NeverSavePasswordInternal() { 190 DCHECK(form_manager_.get()); 191 form_manager_->PermanentlyBlacklist(); 192 } 193 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. 198 // 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()); 207 if (password_store) 208 password_store->RemoveLogin(*password_form_map_.begin()->second); 209 state_ = password_manager::ui::MANAGE_STATE; 210 UpdateBubbleAndIconVisibility(); 211 } 212 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) 218 return; 219 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)) 223 return; 224 225 // Otherwise, reset the password manager and the timer. 226 state_ = password_manager::ui::INACTIVE_STATE; 227 UpdateBubbleAndIconVisibility(); 228 timer_.reset(new base::ElapsedTimer()); 229 } 230 231 void ManagePasswordsUIController::WasHidden() { 232 #if !defined(OS_ANDROID) 233 chrome::CloseManagePasswordsBubble(web_contents()); 234 #endif 235 } 236 237 const autofill::PasswordForm& ManagePasswordsUIController:: 238 PendingCredentials() const { 239 DCHECK(form_manager_); 240 return form_manager_->pending_credentials(); 241 } 242 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 250 // user action. 251 password_manager::ui::State end_state = 252 GetEndStateForAutomaticState(state_); 253 icon->SetState(end_state); 254 ShowBubbleWithoutUserInteraction(); 255 state_ = end_state; 256 } else { 257 icon->SetState(state_); 258 } 259 } 260 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()) 266 return; 267 CommandUpdater* updater = browser->command_controller()->command_updater(); 268 updater->ExecuteCommand(IDC_MANAGE_PASSWORDS_FOR_PAGE); 269 #endif 270 } 271 272 bool ManagePasswordsUIController::PasswordPendingUserDecision() const { 273 return state_ == password_manager::ui::PENDING_PASSWORD_STATE || 274 state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE; 275 } 276