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