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 34 namespace { 35 36 const int kDesiredBubbleWidth = 370; 37 38 enum 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|. 57 void 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. 107 void 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 126 namespace chrome { 127 128 void 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 144 ManagePasswordsBubbleView::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 186 ManagePasswordsBubbleView::PendingView::~PendingView() { 187 } 188 189 void 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 197 void 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 213 ManagePasswordsBubbleView::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 270 ManagePasswordsBubbleView::ConfirmNeverView::~ConfirmNeverView() { 271 } 272 273 void 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 285 ManagePasswordsBubbleView::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 352 ManagePasswordsBubbleView::ManageView::~ManageView() { 353 } 354 355 void 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 363 void 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 372 ManagePasswordsBubbleView::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 415 ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() { 416 } 417 418 void 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 433 ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ = 434 NULL; 435 436 // static 437 void 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 } 470 471 // static 472 void ManagePasswordsBubbleView::CloseBubble() { 473 if (manage_passwords_bubble_) 474 manage_passwords_bubble_->Close(); 475 } 476 477 // static 478 bool ManagePasswordsBubbleView::IsShowing() { 479 // The bubble may be in the process of closing. 480 return (manage_passwords_bubble_ != NULL) && 481 manage_passwords_bubble_->GetWidget()->IsVisible(); 482 } 483 484 ManagePasswordsBubbleView::ManagePasswordsBubbleView( 485 content::WebContents* web_contents, 486 ManagePasswordsIconView* anchor_view, 487 DisplayReason reason) 488 : ManagePasswordsBubble(web_contents, reason), 489 BubbleDelegateView(anchor_view, 490 anchor_view ? views::BubbleBorder::TOP_RIGHT 491 : views::BubbleBorder::NONE), 492 anchor_view_(anchor_view), 493 never_save_passwords_(false) { 494 // Compensate for built-in vertical padding in the anchor view's image. 495 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 496 set_notify_enter_exit_on_child(true); 497 if (anchor_view) 498 anchor_view->SetActive(true); 499 } 500 501 ManagePasswordsBubbleView::~ManagePasswordsBubbleView() { 502 if (anchor_view_) 503 anchor_view_->SetActive(false); 504 } 505 506 void ManagePasswordsBubbleView::AdjustForFullscreen( 507 const gfx::Rect& screen_bounds) { 508 if (GetAnchorView()) 509 return; 510 511 // The bubble's padding from the screen edge, used in fullscreen. 512 const int kFullscreenPaddingEnd = 20; 513 const size_t bubble_half_width = width() / 2; 514 const int x_pos = base::i18n::IsRTL() ? 515 screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd : 516 screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd; 517 SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0)); 518 } 519 520 void ManagePasswordsBubbleView::Close() { 521 GetWidget()->Close(); 522 } 523 524 void ManagePasswordsBubbleView::Init() { 525 views::FillLayout* layout = new views::FillLayout(); 526 SetLayoutManager(layout); 527 SetFocusable(true); 528 529 Refresh(); 530 } 531 532 void ManagePasswordsBubbleView::WindowClosing() { 533 // Close() closes the window asynchronously, so by the time we reach here, 534 // |manage_passwords_bubble_| may have already been reset. 535 if (manage_passwords_bubble_ == this) 536 manage_passwords_bubble_ = NULL; 537 } 538 539 void ManagePasswordsBubbleView::Refresh() { 540 RemoveAllChildViews(true); 541 if (password_manager::ui::IsPendingState(model()->state())) { 542 if (never_save_passwords_) 543 AddChildView(new ConfirmNeverView(this)); 544 else 545 AddChildView(new PendingView(this)); 546 } else if (model()->state() == password_manager::ui::BLACKLIST_STATE) { 547 AddChildView(new BlacklistedView(this)); 548 } else { 549 AddChildView(new ManageView(this)); 550 } 551 GetLayoutManager()->Layout(this); 552 } 553 554 void ManagePasswordsBubbleView::NotifyNeverForThisSiteClicked() { 555 if (model()->best_matches().empty()) { 556 // Skip confirmation if there are no existing passwords for this site. 557 NotifyConfirmedNeverForThisSite(); 558 } else { 559 never_save_passwords_ = true; 560 Refresh(); 561 } 562 } 563 564 void ManagePasswordsBubbleView::NotifyConfirmedNeverForThisSite() { 565 model()->OnNeverForThisSiteClicked(); 566 Close(); 567 } 568 569 void ManagePasswordsBubbleView::NotifyUndoNeverForThisSite() { 570 never_save_passwords_ = false; 571 Refresh(); 572 } 573