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