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/autofill/password_generation_popup_controller_impl.h" 6 7 #include <math.h> 8 9 #include "base/strings/string_split.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversion_utils.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/browser/ui/autofill/password_generation_popup_observer.h" 14 #include "chrome/browser/ui/autofill/password_generation_popup_view.h" 15 #include "chrome/browser/ui/autofill/popup_constants.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/browser/ui/browser_finder.h" 18 #include "chrome/common/url_constants.h" 19 #include "components/autofill/content/common/autofill_messages.h" 20 #include "components/autofill/core/browser/password_generator.h" 21 #include "components/password_manager/core/browser/password_manager.h" 22 #include "content/public/browser/native_web_keyboard_event.h" 23 #include "content/public/browser/render_view_host.h" 24 #include "content/public/browser/web_contents.h" 25 #include "grit/chromium_strings.h" 26 #include "grit/generated_resources.h" 27 #include "grit/google_chrome_strings.h" 28 #include "ui/base/l10n/l10n_util.h" 29 #include "ui/base/resource/resource_bundle.h" 30 #include "ui/events/keycodes/keyboard_codes.h" 31 #include "ui/gfx/rect_conversions.h" 32 #include "ui/gfx/text_utils.h" 33 34 namespace autofill { 35 36 base::WeakPtr<PasswordGenerationPopupControllerImpl> 37 PasswordGenerationPopupControllerImpl::GetOrCreate( 38 base::WeakPtr<PasswordGenerationPopupControllerImpl> previous, 39 const gfx::RectF& bounds, 40 const PasswordForm& form, 41 int max_length, 42 password_manager::PasswordManager* password_manager, 43 PasswordGenerationPopupObserver* observer, 44 content::WebContents* web_contents, 45 gfx::NativeView container_view) { 46 if (previous.get() && 47 previous->element_bounds() == bounds && 48 previous->web_contents() == web_contents && 49 previous->container_view() == container_view) { 50 return previous; 51 } 52 53 if (previous.get()) 54 previous->Hide(); 55 56 PasswordGenerationPopupControllerImpl* controller = 57 new PasswordGenerationPopupControllerImpl( 58 bounds, 59 form, 60 max_length, 61 password_manager, 62 observer, 63 web_contents, 64 container_view); 65 return controller->GetWeakPtr(); 66 } 67 68 PasswordGenerationPopupControllerImpl::PasswordGenerationPopupControllerImpl( 69 const gfx::RectF& bounds, 70 const PasswordForm& form, 71 int max_length, 72 password_manager::PasswordManager* password_manager, 73 PasswordGenerationPopupObserver* observer, 74 content::WebContents* web_contents, 75 gfx::NativeView container_view) 76 : form_(form), 77 password_manager_(password_manager), 78 observer_(observer), 79 generator_(new PasswordGenerator(max_length)), 80 controller_common_(bounds, container_view, web_contents), 81 view_(NULL), 82 font_list_(ResourceBundle::GetSharedInstance().GetFontList( 83 ResourceBundle::SmallFont)), 84 password_selected_(false), 85 display_password_(false), 86 weak_ptr_factory_(this) { 87 controller_common_.SetKeyPressCallback( 88 base::Bind(&PasswordGenerationPopupControllerImpl::HandleKeyPressEvent, 89 base::Unretained(this))); 90 91 std::vector<base::string16> pieces; 92 base::SplitStringDontTrim( 93 l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_PROMPT), 94 '|', // separator 95 &pieces); 96 DCHECK_EQ(3u, pieces.size()); 97 link_range_ = gfx::Range(pieces[0].size(), 98 pieces[0].size() + pieces[1].size()); 99 help_text_ = JoinString(pieces, base::string16()); 100 } 101 102 PasswordGenerationPopupControllerImpl::~PasswordGenerationPopupControllerImpl() 103 {} 104 105 base::WeakPtr<PasswordGenerationPopupControllerImpl> 106 PasswordGenerationPopupControllerImpl::GetWeakPtr() { 107 return weak_ptr_factory_.GetWeakPtr(); 108 } 109 110 bool PasswordGenerationPopupControllerImpl::HandleKeyPressEvent( 111 const content::NativeWebKeyboardEvent& event) { 112 switch (event.windowsKeyCode) { 113 case ui::VKEY_UP: 114 case ui::VKEY_DOWN: 115 PasswordSelected(true); 116 return true; 117 case ui::VKEY_ESCAPE: 118 Hide(); 119 return true; 120 case ui::VKEY_RETURN: 121 case ui::VKEY_TAB: 122 // We suppress tab if the password is selected because we will 123 // automatically advance focus anyway. 124 return PossiblyAcceptPassword(); 125 default: 126 return false; 127 } 128 } 129 130 bool PasswordGenerationPopupControllerImpl::PossiblyAcceptPassword() { 131 if (password_selected_) { 132 PasswordAccepted(); // This will delete |this|. 133 return true; 134 } 135 136 return false; 137 } 138 139 void PasswordGenerationPopupControllerImpl::PasswordSelected(bool selected) { 140 if (!display_password_ || selected == password_selected_) 141 return; 142 143 password_selected_ = selected; 144 view_->PasswordSelectionUpdated(); 145 view_->UpdateBoundsAndRedrawPopup(); 146 } 147 148 void PasswordGenerationPopupControllerImpl::PasswordAccepted() { 149 if (!display_password_) 150 return; 151 152 web_contents()->GetRenderViewHost()->Send( 153 new AutofillMsg_GeneratedPasswordAccepted( 154 web_contents()->GetRenderViewHost()->GetRoutingID(), 155 current_password_)); 156 password_manager_->SetFormHasGeneratedPassword(form_); 157 Hide(); 158 } 159 160 int PasswordGenerationPopupControllerImpl::GetDesiredWidth() { 161 // Minimum width in pixels. 162 const int minimum_required_width = 300; 163 164 // If the width of the field is longer than the minimum, use that instead. 165 int width = std::max(minimum_required_width, 166 controller_common_.RoundedElementBounds().width()); 167 168 if (display_password_) { 169 // Make sure that the width will always be large enough to display the 170 // password and suggestion on one line. 171 width = std::max(width, 172 gfx::GetStringWidth(current_password_ + SuggestedText(), 173 font_list_) + 2 * kHorizontalPadding); 174 } 175 176 return width; 177 } 178 179 int PasswordGenerationPopupControllerImpl::GetDesiredHeight(int width) { 180 // Note that this wrapping isn't exactly what the popup will do. It shouldn't 181 // line break in the middle of the link, but as long as the link isn't longer 182 // than given width this shouldn't affect the height calculated here. The 183 // default width should be wide enough to prevent this from being an issue. 184 int total_length = gfx::GetStringWidth(HelpText(), font_list_); 185 int usable_width = width - 2 * kHorizontalPadding; 186 int text_height = 187 static_cast<int>(ceil(static_cast<double>(total_length)/usable_width)) * 188 font_list_.GetFontSize(); 189 int help_section_height = text_height + 2 * kHelpVerticalPadding; 190 191 int password_section_height = 0; 192 if (display_password_) { 193 password_section_height = 194 font_list_.GetFontSize() + 2 * kPasswordVerticalPadding; 195 } 196 197 return (2 * kPopupBorderThickness + 198 help_section_height + 199 password_section_height); 200 } 201 202 void PasswordGenerationPopupControllerImpl::CalculateBounds() { 203 int popup_width = GetDesiredWidth(); 204 int popup_height = GetDesiredHeight(popup_width); 205 206 popup_bounds_ = controller_common_.GetPopupBounds(popup_height, popup_width); 207 int sub_view_width = popup_bounds_.width() - 2 * kPopupBorderThickness; 208 209 // Calculate the bounds for the rest of the elements given the bounds of 210 // the popup. 211 if (display_password_) { 212 password_bounds_ = gfx::Rect( 213 kPopupBorderThickness, 214 kPopupBorderThickness, 215 sub_view_width, 216 font_list_.GetFontSize() + 2 * kPasswordVerticalPadding); 217 218 divider_bounds_ = gfx::Rect(kPopupBorderThickness, 219 password_bounds_.bottom(), 220 sub_view_width, 221 1 /* divider heigth*/); 222 } else { 223 password_bounds_ = gfx::Rect(); 224 divider_bounds_ = gfx::Rect(); 225 } 226 227 int help_y = std::max(kPopupBorderThickness, divider_bounds_.bottom()); 228 int help_height = 229 popup_bounds_.height() - help_y - kPopupBorderThickness; 230 help_bounds_ = gfx::Rect( 231 kPopupBorderThickness, 232 help_y, 233 sub_view_width, 234 help_height); 235 } 236 237 void PasswordGenerationPopupControllerImpl::Show(bool display_password) { 238 display_password_ = display_password; 239 if (display_password_) 240 current_password_ = base::ASCIIToUTF16(generator_->Generate()); 241 242 CalculateBounds(); 243 244 if (!view_) { 245 view_ = PasswordGenerationPopupView::Create(this); 246 view_->Show(); 247 } else { 248 view_->UpdateBoundsAndRedrawPopup(); 249 } 250 251 controller_common_.RegisterKeyPressCallback(); 252 253 if (observer_) 254 observer_->OnPopupShown(display_password_); 255 } 256 257 void PasswordGenerationPopupControllerImpl::HideAndDestroy() { 258 Hide(); 259 } 260 261 void PasswordGenerationPopupControllerImpl::Hide() { 262 controller_common_.RemoveKeyPressCallback(); 263 264 if (view_) 265 view_->Hide(); 266 267 if (observer_) 268 observer_->OnPopupHidden(); 269 270 delete this; 271 } 272 273 void PasswordGenerationPopupControllerImpl::ViewDestroyed() { 274 view_ = NULL; 275 276 Hide(); 277 } 278 279 void PasswordGenerationPopupControllerImpl::OnSavedPasswordsLinkClicked() { 280 // TODO(gcasto): Change this to navigate to account central once passwords 281 // are visible there. 282 Browser* browser = 283 chrome::FindBrowserWithWebContents(controller_common_.web_contents()); 284 content::OpenURLParams params( 285 GURL(chrome::kAutoPasswordGenerationLearnMoreURL), content::Referrer(), 286 NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, false); 287 browser->OpenURL(params); 288 } 289 290 void PasswordGenerationPopupControllerImpl::SetSelectionAtPoint( 291 const gfx::Point& point) { 292 PasswordSelected(password_bounds_.Contains(point)); 293 } 294 295 bool PasswordGenerationPopupControllerImpl::AcceptSelectedLine() { 296 if (!password_selected_) 297 return false; 298 299 PasswordAccepted(); 300 return true; 301 } 302 303 void PasswordGenerationPopupControllerImpl::SelectionCleared() { 304 PasswordSelected(false); 305 } 306 307 gfx::NativeView PasswordGenerationPopupControllerImpl::container_view() { 308 return controller_common_.container_view(); 309 } 310 311 const gfx::FontList& PasswordGenerationPopupControllerImpl::font_list() const { 312 return font_list_; 313 } 314 315 const gfx::Rect& PasswordGenerationPopupControllerImpl::popup_bounds() const { 316 return popup_bounds_; 317 } 318 319 const gfx::Rect& PasswordGenerationPopupControllerImpl::password_bounds() 320 const { 321 return password_bounds_; 322 } 323 324 const gfx::Rect& PasswordGenerationPopupControllerImpl::divider_bounds() 325 const { 326 return divider_bounds_; 327 } 328 329 const gfx::Rect& PasswordGenerationPopupControllerImpl::help_bounds() const { 330 return help_bounds_; 331 } 332 333 bool PasswordGenerationPopupControllerImpl::display_password() const { 334 return display_password_; 335 } 336 337 bool PasswordGenerationPopupControllerImpl::password_selected() const { 338 return password_selected_; 339 } 340 341 base::string16 PasswordGenerationPopupControllerImpl::password() const { 342 return current_password_; 343 } 344 345 base::string16 PasswordGenerationPopupControllerImpl::SuggestedText() { 346 return l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION); 347 } 348 349 const base::string16& PasswordGenerationPopupControllerImpl::HelpText() { 350 return help_text_; 351 } 352 353 const gfx::Range& PasswordGenerationPopupControllerImpl::HelpTextLinkRange() { 354 return link_range_; 355 } 356 357 } // namespace autofill 358