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