manage_passwords_bubble_view.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
1// Copyright 2013 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/views/passwords/manage_passwords_bubble_view.h"
6
7#include "chrome/browser/chrome_notification_types.h"
8#include "chrome/browser/ui/browser.h"
9#include "chrome/browser/ui/browser_finder.h"
10#include "chrome/browser/ui/browser_window.h"
11#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
12#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
13#include "chrome/browser/ui/views/frame/browser_view.h"
14#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
15#include "chrome/browser/ui/views/passwords/manage_password_item_view.h"
16#include "chrome/browser/ui/views/passwords/manage_passwords_icon_view.h"
17#include "components/password_manager/core/common/password_manager_ui.h"
18#include "content/public/browser/notification_source.h"
19#include "grit/generated_resources.h"
20#include "ui/base/l10n/l10n_util.h"
21#include "ui/base/models/combobox_model.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "ui/gfx/text_utils.h"
24#include "ui/views/controls/button/blue_button.h"
25#include "ui/views/controls/button/label_button.h"
26#include "ui/views/controls/combobox/combobox.h"
27#include "ui/views/layout/fill_layout.h"
28#include "ui/views/layout/grid_layout.h"
29#include "ui/views/layout/layout_constants.h"
30
31
32// Helpers --------------------------------------------------------------------
33
34namespace {
35
36const int kDesiredBubbleWidth = 370;
37
38enum ColumnSetType {
39  // | | (FILL, FILL) | |
40  // Used for the bubble's header, the credentials list, and for simple
41  // messages like "No passwords".
42  SINGLE_VIEW_COLUMN_SET = 0,
43
44  // | | (TRAILING, CENTER) | | (TRAILING, CENTER) | |
45  // Used for buttons at the bottom of the bubble which should nest at the
46  // bottom-right corner.
47  DOUBLE_BUTTON_COLUMN_SET = 1,
48
49  // | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
50  // Used for buttons at the bottom of the bubble which should occupy
51  // the corners.
52  LINK_BUTTON_COLUMN_SET = 2,
53};
54
55// Construct an appropriate ColumnSet for the given |type|, and add it
56// to |layout|.
57void BuildColumnSet(views::GridLayout* layout, ColumnSetType type) {
58  views::ColumnSet* column_set = layout->AddColumnSet(type);
59  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
60  int full_width = kDesiredBubbleWidth - (2 * views::kPanelHorizMargin);
61  switch (type) {
62    case SINGLE_VIEW_COLUMN_SET:
63      column_set->AddColumn(views::GridLayout::FILL,
64                            views::GridLayout::FILL,
65                            0,
66                            views::GridLayout::FIXED,
67                            full_width,
68                            0);
69      break;
70
71    case DOUBLE_BUTTON_COLUMN_SET:
72      column_set->AddColumn(views::GridLayout::TRAILING,
73                            views::GridLayout::CENTER,
74                            1,
75                            views::GridLayout::USE_PREF,
76                            0,
77                            0);
78      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
79      column_set->AddColumn(views::GridLayout::TRAILING,
80                            views::GridLayout::CENTER,
81                            0,
82                            views::GridLayout::USE_PREF,
83                            0,
84                            0);
85      break;
86    case LINK_BUTTON_COLUMN_SET:
87      column_set->AddColumn(views::GridLayout::LEADING,
88                            views::GridLayout::CENTER,
89                            1,
90                            views::GridLayout::USE_PREF,
91                            0,
92                            0);
93      column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
94      column_set->AddColumn(views::GridLayout::TRAILING,
95                            views::GridLayout::CENTER,
96                            0,
97                            views::GridLayout::USE_PREF,
98                            0,
99                            0);
100      break;
101  }
102  column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
103}
104
105// Given a layout and a model, add an appropriate title using a
106// SINGLE_VIEW_COLUMN_SET, followed by a spacer row.
107void AddTitleRow(views::GridLayout* layout, ManagePasswordsBubbleModel* model) {
108  views::Label* title_label = new views::Label(model->title());
109  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
110  title_label->SetMultiLine(true);
111  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
112      ui::ResourceBundle::MediumFont));
113
114  // Add the title to the layout with appropriate padding.
115  layout->StartRowWithPadding(
116      0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing);
117  layout->AddView(title_label);
118  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
119}
120
121}  // namespace
122
123
124// Globals --------------------------------------------------------------------
125
126namespace chrome {
127
128void ShowManagePasswordsBubble(content::WebContents* web_contents) {
129  ManagePasswordsUIController* controller =
130      ManagePasswordsUIController::FromWebContents(web_contents);
131  ManagePasswordsBubbleView::ShowBubble(
132      web_contents,
133      controller->state() ==
134              password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
135          ? ManagePasswordsBubbleView::AUTOMATIC
136          : ManagePasswordsBubbleView::USER_ACTION);
137}
138
139}  // namespace chrome
140
141
142// ManagePasswordsBubbleView::PendingView -------------------------------------
143
144ManagePasswordsBubbleView::PendingView::PendingView(
145    ManagePasswordsBubbleView* parent)
146    : parent_(parent) {
147  views::GridLayout* layout = new views::GridLayout(this);
148  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
149  SetLayoutManager(layout);
150
151  // Create the pending credential item, save button and refusal combobox.
152  ManagePasswordItemView* item =
153      new ManagePasswordItemView(parent->model(),
154                                 parent->model()->pending_credentials(),
155                                 ManagePasswordItemView::FIRST_ITEM);
156  save_button_ = new views::BlueButton(
157      this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON));
158  save_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
159      ui::ResourceBundle::SmallFont));
160
161  combobox_model_.reset(new SavePasswordRefusalComboboxModel());
162  refuse_combobox_.reset(new views::Combobox(combobox_model_.get()));
163  refuse_combobox_->set_listener(this);
164  refuse_combobox_->SetStyle(views::Combobox::STYLE_ACTION);
165  // TODO(mkwst): Need a mechanism to pipe a font list down into a combobox.
166
167  // Title row.
168  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
169  AddTitleRow(layout, parent_->model());
170
171  // Credential row.
172  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
173  layout->AddView(item);
174
175  // Button row.
176  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
177  layout->StartRowWithPadding(
178      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
179  layout->AddView(save_button_);
180  layout->AddView(refuse_combobox_.get());
181
182  // Extra padding for visual awesomeness.
183  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
184}
185
186ManagePasswordsBubbleView::PendingView::~PendingView() {
187}
188
189void ManagePasswordsBubbleView::PendingView::ButtonPressed(
190    views::Button* sender,
191    const ui::Event& event) {
192  DCHECK(sender == save_button_);
193  parent_->model()->OnSaveClicked();
194  parent_->Close();
195}
196
197void ManagePasswordsBubbleView::PendingView::OnPerformAction(
198    views::Combobox* source) {
199  DCHECK_EQ(source, refuse_combobox_);
200  switch (refuse_combobox_->selected_index()) {
201    case SavePasswordRefusalComboboxModel::INDEX_NOPE:
202      parent_->model()->OnNopeClicked();
203      parent_->Close();
204      break;
205    case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE:
206      parent_->NotifyNeverForThisSiteClicked();
207      break;
208  }
209}
210
211// ManagePasswordsBubbleView::ConfirmNeverView ---------------------------------
212
213ManagePasswordsBubbleView::ConfirmNeverView::ConfirmNeverView(
214    ManagePasswordsBubbleView* parent)
215    : parent_(parent) {
216  views::GridLayout* layout = new views::GridLayout(this);
217  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
218  SetLayoutManager(layout);
219
220  // Title row.
221  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
222  views::Label* title_label = new views::Label(l10n_util::GetStringUTF16(
223      IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE));
224  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
225  title_label->SetMultiLine(true);
226  title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
227      ui::ResourceBundle::MediumFont));
228  layout->StartRowWithPadding(
229      0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing);
230  layout->AddView(title_label);
231  layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing);
232
233  // Confirmation text.
234  views::Label* confirmation = new views::Label(l10n_util::GetStringUTF16(
235      IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TEXT));
236  confirmation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
237  confirmation->SetMultiLine(true);
238  confirmation->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
239      ui::ResourceBundle::SmallFont));
240  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
241  layout->AddView(confirmation);
242  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
243
244  // Confirm and undo buttons.
245  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
246  layout->StartRowWithPadding(
247      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
248
249  confirm_button_ = new views::LabelButton(
250      this,
251      l10n_util::GetStringUTF16(
252          IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_BUTTON));
253  confirm_button_->SetStyle(views::Button::STYLE_BUTTON);
254  confirm_button_->SetFontList(
255      ui::ResourceBundle::GetSharedInstance().GetFontList(
256          ui::ResourceBundle::SmallFont));
257  layout->AddView(confirm_button_);
258
259  undo_button_ =
260      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL));
261  undo_button_->SetStyle(views::Button::STYLE_BUTTON);
262  undo_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
263      ui::ResourceBundle::SmallFont));
264  layout->AddView(undo_button_);
265
266  // Extra padding for visual awesomeness.
267  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
268}
269
270ManagePasswordsBubbleView::ConfirmNeverView::~ConfirmNeverView() {
271}
272
273void ManagePasswordsBubbleView::ConfirmNeverView::ButtonPressed(
274    views::Button* sender,
275    const ui::Event& event) {
276  DCHECK(sender == confirm_button_ || sender == undo_button_);
277  if (sender == confirm_button_)
278    parent_->NotifyConfirmedNeverForThisSite();
279  else
280    parent_->NotifyUndoNeverForThisSite();
281}
282
283// ManagePasswordsBubbleView::ManageView --------------------------------------
284
285ManagePasswordsBubbleView::ManageView::ManageView(
286    ManagePasswordsBubbleView* parent)
287    : parent_(parent) {
288  views::GridLayout* layout = new views::GridLayout(this);
289  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
290  SetLayoutManager(layout);
291
292  // Add the title.
293  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
294  AddTitleRow(layout, parent_->model());
295
296  // If we have a list of passwords to store for the current site, display
297  // them to the user for management. Otherwise, render a "No passwords for
298  // this site" message.
299  if (!parent_->model()->best_matches().empty()) {
300    for (autofill::PasswordFormMap::const_iterator i(
301             parent_->model()->best_matches().begin());
302         i != parent_->model()->best_matches().end();
303         ++i) {
304      ManagePasswordItemView* item = new ManagePasswordItemView(
305          parent_->model(),
306          *i->second,
307          i == parent_->model()->best_matches().begin()
308              ? ManagePasswordItemView::FIRST_ITEM
309              : ManagePasswordItemView::SUBSEQUENT_ITEM);
310
311      layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
312      layout->AddView(item);
313    }
314  } else {
315    views::Label* empty_label = new views::Label(
316        l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS));
317    empty_label->SetMultiLine(true);
318    empty_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
319    empty_label->SetFontList(
320        ui::ResourceBundle::GetSharedInstance().GetFontList(
321            ui::ResourceBundle::SmallFont));
322
323    layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
324    layout->AddView(empty_label);
325    layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
326  }
327
328  // Then add the "manage passwords" link and "Done" button.
329  manage_link_ = new views::Link(parent_->model()->manage_link());
330  manage_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
331  manage_link_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
332      ui::ResourceBundle::SmallFont));
333  manage_link_->SetUnderline(false);
334  manage_link_->set_listener(this);
335
336  done_button_ =
337      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
338  done_button_->SetStyle(views::Button::STYLE_BUTTON);
339  done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
340      ui::ResourceBundle::SmallFont));
341
342  BuildColumnSet(layout, LINK_BUTTON_COLUMN_SET);
343  layout->StartRowWithPadding(
344      0, LINK_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
345  layout->AddView(manage_link_);
346  layout->AddView(done_button_);
347
348  // Extra padding for visual awesomeness.
349  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
350}
351
352ManagePasswordsBubbleView::ManageView::~ManageView() {
353}
354
355void ManagePasswordsBubbleView::ManageView::ButtonPressed(
356    views::Button* sender,
357    const ui::Event& event) {
358  DCHECK(sender == done_button_);
359  parent_->model()->OnDoneClicked();
360  parent_->Close();
361}
362
363void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link* source,
364                                                        int event_flags) {
365  DCHECK_EQ(source, manage_link_);
366  parent_->model()->OnManageLinkClicked();
367  parent_->Close();
368}
369
370// ManagePasswordsBubbleView::BlacklistedView ---------------------------------
371
372ManagePasswordsBubbleView::BlacklistedView::BlacklistedView(
373    ManagePasswordsBubbleView* parent)
374    : parent_(parent) {
375  views::GridLayout* layout = new views::GridLayout(this);
376  layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
377  SetLayoutManager(layout);
378
379  // Add the title.
380  BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
381  AddTitleRow(layout, parent_->model());
382
383  // Add the "Hey! You blacklisted this site!" text.
384  views::Label* blacklisted = new views::Label(
385      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED));
386  blacklisted->SetMultiLine(true);
387  blacklisted->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
388      ui::ResourceBundle::SmallFont));
389  layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
390  layout->AddView(blacklisted);
391  layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
392
393  // Then add the "enable password manager" and "Done" buttons.
394  unblacklist_button_ = new views::BlueButton(
395      this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UNBLACKLIST_BUTTON));
396  unblacklist_button_->SetFontList(
397      ui::ResourceBundle::GetSharedInstance().GetFontList(
398          ui::ResourceBundle::SmallFont));
399  done_button_ =
400      new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
401  done_button_->SetStyle(views::Button::STYLE_BUTTON);
402  done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList(
403      ui::ResourceBundle::SmallFont));
404
405  BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
406  layout->StartRowWithPadding(
407      0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
408  layout->AddView(unblacklist_button_);
409  layout->AddView(done_button_);
410
411  // Extra padding for visual awesomeness.
412  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
413}
414
415ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() {
416}
417
418void ManagePasswordsBubbleView::BlacklistedView::ButtonPressed(
419    views::Button* sender,
420    const ui::Event& event) {
421  if (sender == done_button_)
422    parent_->model()->OnDoneClicked();
423  else if (sender == unblacklist_button_)
424    parent_->model()->OnUnblacklistClicked();
425  else
426    NOTREACHED();
427  parent_->Close();
428}
429
430// ManagePasswordsBubbleView --------------------------------------------------
431
432// static
433ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ =
434    NULL;
435
436// static
437void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents,
438                                           DisplayReason reason) {
439  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
440  DCHECK(browser);
441  DCHECK(browser->window());
442  DCHECK(browser->fullscreen_controller());
443
444  if (IsShowing())
445    return;
446
447  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
448  bool is_fullscreen = browser_view->IsFullscreen();
449  ManagePasswordsIconView* anchor_view =
450      is_fullscreen
451          ? NULL
452          : browser_view->GetLocationBarView()->manage_passwords_icon_view();
453  manage_passwords_bubble_ = new ManagePasswordsBubbleView(
454      web_contents, anchor_view, reason);
455
456  if (is_fullscreen) {
457    manage_passwords_bubble_->set_parent_window(
458        web_contents->GetTopLevelNativeWindow());
459  }
460
461  views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_);
462
463  // Adjust for fullscreen after creation as it relies on the content size.
464  if (is_fullscreen) {
465    manage_passwords_bubble_->AdjustForFullscreen(
466        browser_view->GetBoundsInScreen());
467  }
468  manage_passwords_bubble_->GetWidget()->Show();
469  manage_passwords_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
470}
471
472// static
473void ManagePasswordsBubbleView::CloseBubble() {
474  if (manage_passwords_bubble_)
475    manage_passwords_bubble_->Close();
476}
477
478// static
479bool ManagePasswordsBubbleView::IsShowing() {
480  // The bubble may be in the process of closing.
481  return (manage_passwords_bubble_ != NULL) &&
482      manage_passwords_bubble_->GetWidget()->IsVisible();
483}
484
485ManagePasswordsBubbleView::ManagePasswordsBubbleView(
486    content::WebContents* web_contents,
487    ManagePasswordsIconView* anchor_view,
488    DisplayReason reason)
489    : ManagePasswordsBubble(web_contents, reason),
490      BubbleDelegateView(anchor_view,
491                         anchor_view ? views::BubbleBorder::TOP_RIGHT
492                                     : views::BubbleBorder::NONE),
493      anchor_view_(anchor_view),
494      never_save_passwords_(false) {
495  // Compensate for built-in vertical padding in the anchor view's image.
496  set_anchor_view_insets(gfx::Insets(2, 0, 2, 0));
497  set_notify_enter_exit_on_child(true);
498  if (anchor_view)
499    anchor_view->SetActive(true);
500}
501
502ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {
503  if (anchor_view_)
504    anchor_view_->SetActive(false);
505}
506
507void ManagePasswordsBubbleView::AdjustForFullscreen(
508    const gfx::Rect& screen_bounds) {
509  if (GetAnchorView())
510    return;
511
512  // The bubble's padding from the screen edge, used in fullscreen.
513  const int kFullscreenPaddingEnd = 20;
514  const size_t bubble_half_width = width() / 2;
515  const int x_pos = base::i18n::IsRTL() ?
516      screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd :
517      screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd;
518  SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0));
519}
520
521void ManagePasswordsBubbleView::Close() {
522  GetWidget()->Close();
523}
524
525void ManagePasswordsBubbleView::Init() {
526  views::FillLayout* layout = new views::FillLayout();
527  SetLayoutManager(layout);
528  SetFocusable(true);
529
530  Refresh();
531}
532
533void ManagePasswordsBubbleView::WindowClosing() {
534  // Close() closes the window asynchronously, so by the time we reach here,
535  // |manage_passwords_bubble_| may have already been reset.
536  if (manage_passwords_bubble_ == this)
537    manage_passwords_bubble_ = NULL;
538}
539
540void ManagePasswordsBubbleView::Refresh() {
541  RemoveAllChildViews(true);
542  if (password_manager::ui::IsPendingState(model()->state())) {
543    if (never_save_passwords_)
544      AddChildView(new ConfirmNeverView(this));
545    else
546      AddChildView(new PendingView(this));
547  } else if (model()->state() == password_manager::ui::BLACKLIST_STATE) {
548    AddChildView(new BlacklistedView(this));
549  } else {
550    AddChildView(new ManageView(this));
551  }
552  GetLayoutManager()->Layout(this);
553}
554
555void ManagePasswordsBubbleView::NotifyNeverForThisSiteClicked() {
556  if (model()->best_matches().empty()) {
557    // Skip confirmation if there are no existing passwords for this site.
558    NotifyConfirmedNeverForThisSite();
559  } else {
560    never_save_passwords_ = true;
561    Refresh();
562  }
563}
564
565void ManagePasswordsBubbleView::NotifyConfirmedNeverForThisSite() {
566  model()->OnNeverForThisSiteClicked();
567  Close();
568}
569
570void ManagePasswordsBubbleView::NotifyUndoNeverForThisSite() {
571  never_save_passwords_ = false;
572  Refresh();
573}
574