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 22using autofill::PasswordFormMap; 23using password_manager::PasswordFormManager; 24 25namespace { 26 27password_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 34autofill::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. 43void 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 57DEFINE_WEB_CONTENTS_USER_DATA_KEY(ManagePasswordsUIController); 58 59ManagePasswordsUIController::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 69ManagePasswordsUIController::~ManagePasswordsUIController() {} 70 71void 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 89void 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 98void 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 109void 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 117void 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 125void ManagePasswordsUIController::WebContentsDestroyed() { 126 password_manager::PasswordStore* password_store = 127 GetPasswordStore(web_contents()); 128 if (password_store) 129 password_store->RemoveObserver(this); 130} 131 132void 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 159void 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 170void ManagePasswordsUIController::SavePassword() { 171 DCHECK(PasswordPendingUserDecision()); 172 SavePasswordInternal(); 173 state_ = password_manager::ui::MANAGE_STATE; 174 UpdateBubbleAndIconVisibility(); 175} 176 177void ManagePasswordsUIController::SavePasswordInternal() { 178 DCHECK(form_manager_.get()); 179 form_manager_->Save(); 180} 181 182void ManagePasswordsUIController::NeverSavePassword() { 183 DCHECK(PasswordPendingUserDecision()); 184 NeverSavePasswordInternal(); 185 state_ = password_manager::ui::BLACKLIST_STATE; 186 UpdateBubbleAndIconVisibility(); 187} 188 189void ManagePasswordsUIController::NeverSavePasswordInternal() { 190 DCHECK(form_manager_.get()); 191 form_manager_->PermanentlyBlacklist(); 192} 193 194void 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 213void 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 231void ManagePasswordsUIController::WasHidden() { 232#if !defined(OS_ANDROID) 233 chrome::CloseManagePasswordsBubble(web_contents()); 234#endif 235} 236 237const autofill::PasswordForm& ManagePasswordsUIController:: 238 PendingCredentials() const { 239 DCHECK(form_manager_); 240 return form_manager_->pending_credentials(); 241} 242 243void 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 261void 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 272bool ManagePasswordsUIController::PasswordPendingUserDecision() const { 273 return state_ == password_manager::ui::PENDING_PASSWORD_STATE || 274 state_ == password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE; 275} 276