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