password_generation_popup_controller_impl.cc revision 010d83a9304c5a91596085d917d248abff47903a
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 34namespace autofill { 35 36base::WeakPtr<PasswordGenerationPopupControllerImpl> 37PasswordGenerationPopupControllerImpl::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 68PasswordGenerationPopupControllerImpl::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 102PasswordGenerationPopupControllerImpl::~PasswordGenerationPopupControllerImpl() 103 {} 104 105base::WeakPtr<PasswordGenerationPopupControllerImpl> 106PasswordGenerationPopupControllerImpl::GetWeakPtr() { 107 return weak_ptr_factory_.GetWeakPtr(); 108} 109 110bool 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 130bool PasswordGenerationPopupControllerImpl::PossiblyAcceptPassword() { 131 if (password_selected_) { 132 PasswordAccepted(); // This will delete |this|. 133 return true; 134 } 135 136 return false; 137} 138 139void PasswordGenerationPopupControllerImpl::PasswordSelected(bool selected) { 140 if (!display_password_) 141 return; 142 143 password_selected_ = selected; 144 view_->PasswordSelectionUpdated(); 145 view_->UpdateBoundsAndRedrawPopup(); 146} 147 148void 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 160int 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 179int 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 202void 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 237void 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 257void PasswordGenerationPopupControllerImpl::HideAndDestroy() { 258 Hide(); 259} 260 261void 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 273void PasswordGenerationPopupControllerImpl::ViewDestroyed() { 274 view_ = NULL; 275 276 Hide(); 277} 278 279void 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 290void PasswordGenerationPopupControllerImpl::SetSelectionAtPoint( 291 const gfx::Point& point) { 292 if (password_bounds_.Contains(point)) 293 PasswordSelected(true); 294} 295 296bool PasswordGenerationPopupControllerImpl::AcceptSelectedLine() { 297 if (!password_selected_) 298 return false; 299 300 PasswordAccepted(); 301 return true; 302} 303 304void PasswordGenerationPopupControllerImpl::SelectionCleared() { 305 PasswordSelected(false); 306} 307 308gfx::NativeView PasswordGenerationPopupControllerImpl::container_view() { 309 return controller_common_.container_view(); 310} 311 312const gfx::FontList& PasswordGenerationPopupControllerImpl::font_list() const { 313 return font_list_; 314} 315 316const gfx::Rect& PasswordGenerationPopupControllerImpl::popup_bounds() const { 317 return popup_bounds_; 318} 319 320const gfx::Rect& PasswordGenerationPopupControllerImpl::password_bounds() 321 const { 322 return password_bounds_; 323} 324 325const gfx::Rect& PasswordGenerationPopupControllerImpl::divider_bounds() 326 const { 327 return divider_bounds_; 328} 329 330const gfx::Rect& PasswordGenerationPopupControllerImpl::help_bounds() const { 331 return help_bounds_; 332} 333 334bool PasswordGenerationPopupControllerImpl::display_password() const { 335 return display_password_; 336} 337 338bool PasswordGenerationPopupControllerImpl::password_selected() const { 339 return password_selected_; 340} 341 342base::string16 PasswordGenerationPopupControllerImpl::password() const { 343 return current_password_; 344} 345 346base::string16 PasswordGenerationPopupControllerImpl::SuggestedText() { 347 return l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION); 348} 349 350const base::string16& PasswordGenerationPopupControllerImpl::HelpText() { 351 return help_text_; 352} 353 354const gfx::Range& PasswordGenerationPopupControllerImpl::HelpTextLinkRange() { 355 return link_range_; 356} 357 358} // namespace autofill 359