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