Home | History | Annotate | Download | only in autofill
      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