manage_passwords_bubble_view.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/ui/browser.h" 8#include "chrome/browser/ui/browser_finder.h" 9#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" 10#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h" 11#include "chrome/browser/ui/passwords/save_password_refusal_combobox_model.h" 12#include "chrome/browser/ui/views/frame/browser_view.h" 13#include "chrome/browser/ui/views/location_bar/location_bar_view.h" 14#include "chrome/browser/ui/views/passwords/manage_password_item_view.h" 15#include "chrome/browser/ui/views/passwords/manage_passwords_icon_view.h" 16#include "chrome/grit/generated_resources.h" 17#include "content/public/browser/render_view_host.h" 18#include "content/public/browser/web_contents.h" 19#include "ui/aura/window.h" 20#include "ui/base/l10n/l10n_util.h" 21#include "ui/base/resource/resource_bundle.h" 22#include "ui/views/controls/button/blue_button.h" 23#include "ui/views/controls/button/label_button.h" 24#include "ui/views/controls/combobox/combobox.h" 25#include "ui/views/controls/combobox/combobox_listener.h" 26#include "ui/views/controls/link.h" 27#include "ui/views/controls/link_listener.h" 28#include "ui/views/controls/styled_label.h" 29#include "ui/views/controls/styled_label_listener.h" 30#include "ui/views/layout/fill_layout.h" 31#include "ui/views/layout/grid_layout.h" 32#include "ui/views/layout/layout_constants.h" 33 34 35// Helpers -------------------------------------------------------------------- 36 37namespace { 38 39const int kDesiredBubbleWidth = 370; 40 41enum ColumnSetType { 42 // | | (FILL, FILL) | | 43 // Used for the bubble's header, the credentials list, and for simple 44 // messages like "No passwords". 45 SINGLE_VIEW_COLUMN_SET = 0, 46 47 // | | (TRAILING, CENTER) | | (TRAILING, CENTER) | | 48 // Used for buttons at the bottom of the bubble which should nest at the 49 // bottom-right corner. 50 DOUBLE_BUTTON_COLUMN_SET = 1, 51 52 // | | (LEADING, CENTER) | | (TRAILING, CENTER) | | 53 // Used for buttons at the bottom of the bubble which should occupy 54 // the corners. 55 LINK_BUTTON_COLUMN_SET = 2, 56 57 // | | (TRAILING, CENTER) | | 58 // Used when there is only one button which should next at the bottom-right 59 // corner. 60 SINGLE_BUTTON_COLUMN_SET = 3, 61}; 62 63// Construct an appropriate ColumnSet for the given |type|, and add it 64// to |layout|. 65void BuildColumnSet(views::GridLayout* layout, ColumnSetType type) { 66 views::ColumnSet* column_set = layout->AddColumnSet(type); 67 column_set->AddPaddingColumn(0, views::kPanelHorizMargin); 68 int full_width = kDesiredBubbleWidth - (2 * views::kPanelHorizMargin); 69 switch (type) { 70 case SINGLE_VIEW_COLUMN_SET: 71 column_set->AddColumn(views::GridLayout::FILL, 72 views::GridLayout::FILL, 73 0, 74 views::GridLayout::FIXED, 75 full_width, 76 0); 77 break; 78 79 case DOUBLE_BUTTON_COLUMN_SET: 80 column_set->AddColumn(views::GridLayout::TRAILING, 81 views::GridLayout::CENTER, 82 1, 83 views::GridLayout::USE_PREF, 84 0, 85 0); 86 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 87 column_set->AddColumn(views::GridLayout::TRAILING, 88 views::GridLayout::CENTER, 89 0, 90 views::GridLayout::USE_PREF, 91 0, 92 0); 93 break; 94 case LINK_BUTTON_COLUMN_SET: 95 column_set->AddColumn(views::GridLayout::LEADING, 96 views::GridLayout::CENTER, 97 1, 98 views::GridLayout::USE_PREF, 99 0, 100 0); 101 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 102 column_set->AddColumn(views::GridLayout::TRAILING, 103 views::GridLayout::CENTER, 104 0, 105 views::GridLayout::USE_PREF, 106 0, 107 0); 108 break; 109 case SINGLE_BUTTON_COLUMN_SET: 110 column_set->AddColumn(views::GridLayout::TRAILING, 111 views::GridLayout::CENTER, 112 1, 113 views::GridLayout::USE_PREF, 114 0, 115 0); 116 } 117 column_set->AddPaddingColumn(0, views::kPanelHorizMargin); 118} 119 120// Given a layout and a model, add an appropriate title using a 121// SINGLE_VIEW_COLUMN_SET, followed by a spacer row. 122void AddTitleRow(views::GridLayout* layout, ManagePasswordsBubbleModel* model) { 123 views::Label* title_label = new views::Label(model->title()); 124 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 125 title_label->SetMultiLine(true); 126 title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 127 ui::ResourceBundle::MediumFont)); 128 129 // Add the title to the layout with appropriate padding. 130 layout->StartRowWithPadding( 131 0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing); 132 layout->AddView(title_label); 133 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 134} 135 136} // namespace 137 138 139// Globals -------------------------------------------------------------------- 140 141namespace chrome { 142 143void ShowManagePasswordsBubble(content::WebContents* web_contents) { 144 if (ManagePasswordsBubbleView::IsShowing()) { 145 // The bubble is currently shown for some other tab. We should close it now 146 // and open for |web_contents|. 147 ManagePasswordsBubbleView::CloseBubble(); 148 } 149 ManagePasswordsUIController* controller = 150 ManagePasswordsUIController::FromWebContents(web_contents); 151 ManagePasswordsBubbleView::ShowBubble( 152 web_contents, 153 password_manager::ui::IsAutomaticDisplayState(controller->state()) 154 ? ManagePasswordsBubbleView::AUTOMATIC 155 : ManagePasswordsBubbleView::USER_ACTION); 156} 157 158void CloseManagePasswordsBubble(content::WebContents* web_contents) { 159 if (!ManagePasswordsBubbleView::IsShowing()) 160 return; 161 content::WebContents* bubble_web_contents = 162 ManagePasswordsBubbleView::manage_password_bubble()->web_contents(); 163 if (web_contents == bubble_web_contents) 164 ManagePasswordsBubbleView::CloseBubble(); 165} 166 167} // namespace chrome 168 169 170// ManagePasswordsBubbleView::PendingView ------------------------------------- 171 172// A view offering the user the ability to save credentials. Contains a 173// single ManagePasswordItemView, along with a "Save Passwords" button 174// and a rejection combobox. 175class ManagePasswordsBubbleView::PendingView : public views::View, 176 public views::ButtonListener, 177 public views::ComboboxListener { 178 public: 179 explicit PendingView(ManagePasswordsBubbleView* parent); 180 virtual ~PendingView(); 181 182 private: 183 // views::ButtonListener: 184 virtual void ButtonPressed(views::Button* sender, 185 const ui::Event& event) OVERRIDE; 186 187 // Handles the event when the user changes an index of a combobox. 188 virtual void OnPerformAction(views::Combobox* source) OVERRIDE; 189 190 ManagePasswordsBubbleView* parent_; 191 192 views::BlueButton* save_button_; 193 194 // The combobox doesn't take ownership of its model. If we created a 195 // combobox we need to ensure that we delete the model here, and because the 196 // combobox uses the model in it's destructor, we need to make sure we 197 // delete the model _after_ the combobox itself is deleted. 198 scoped_ptr<SavePasswordRefusalComboboxModel> combobox_model_; 199 scoped_ptr<views::Combobox> refuse_combobox_; 200}; 201 202ManagePasswordsBubbleView::PendingView::PendingView( 203 ManagePasswordsBubbleView* parent) 204 : parent_(parent) { 205 views::GridLayout* layout = new views::GridLayout(this); 206 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0)); 207 SetLayoutManager(layout); 208 209 // Create the pending credential item, save button and refusal combobox. 210 ManagePasswordItemView* item = 211 new ManagePasswordItemView(parent->model(), 212 parent->model()->pending_credentials(), 213 password_manager::ui::FIRST_ITEM); 214 save_button_ = new views::BlueButton( 215 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON)); 216 save_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 217 ui::ResourceBundle::SmallFont)); 218 219 combobox_model_.reset(new SavePasswordRefusalComboboxModel()); 220 refuse_combobox_.reset(new views::Combobox(combobox_model_.get())); 221 refuse_combobox_->set_listener(this); 222 refuse_combobox_->SetStyle(views::Combobox::STYLE_ACTION); 223 // TODO(mkwst): Need a mechanism to pipe a font list down into a combobox. 224 225 // Title row. 226 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 227 AddTitleRow(layout, parent_->model()); 228 229 // Credential row. 230 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 231 layout->AddView(item); 232 233 // Button row. 234 BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET); 235 layout->StartRowWithPadding( 236 0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 237 layout->AddView(save_button_); 238 layout->AddView(refuse_combobox_.get()); 239 240 // Extra padding for visual awesomeness. 241 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 242 243 parent_->set_initially_focused_view(save_button_); 244} 245 246ManagePasswordsBubbleView::PendingView::~PendingView() { 247} 248 249void ManagePasswordsBubbleView::PendingView::ButtonPressed( 250 views::Button* sender, 251 const ui::Event& event) { 252 DCHECK(sender == save_button_); 253 parent_->model()->OnSaveClicked(); 254 parent_->Close(); 255} 256 257void ManagePasswordsBubbleView::PendingView::OnPerformAction( 258 views::Combobox* source) { 259 DCHECK_EQ(source, refuse_combobox_); 260 switch (refuse_combobox_->selected_index()) { 261 case SavePasswordRefusalComboboxModel::INDEX_NOPE: 262 parent_->model()->OnNopeClicked(); 263 parent_->Close(); 264 break; 265 case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE: 266 parent_->NotifyNeverForThisSiteClicked(); 267 break; 268 } 269} 270 271// ManagePasswordsBubbleView::ConfirmNeverView --------------------------------- 272 273// A view offering the user the ability to undo her decision to never save 274// passwords for a particular site. 275class ManagePasswordsBubbleView::ConfirmNeverView 276 : public views::View, 277 public views::ButtonListener { 278 public: 279 explicit ConfirmNeverView(ManagePasswordsBubbleView* parent); 280 virtual ~ConfirmNeverView(); 281 282 private: 283 // views::ButtonListener: 284 virtual void ButtonPressed(views::Button* sender, 285 const ui::Event& event) OVERRIDE; 286 287 ManagePasswordsBubbleView* parent_; 288 289 views::LabelButton* confirm_button_; 290 views::LabelButton* undo_button_; 291}; 292 293ManagePasswordsBubbleView::ConfirmNeverView::ConfirmNeverView( 294 ManagePasswordsBubbleView* parent) 295 : parent_(parent) { 296 views::GridLayout* layout = new views::GridLayout(this); 297 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0)); 298 SetLayoutManager(layout); 299 300 // Title row. 301 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 302 views::Label* title_label = new views::Label(l10n_util::GetStringUTF16( 303 IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TITLE)); 304 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 305 title_label->SetMultiLine(true); 306 title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 307 ui::ResourceBundle::MediumFont)); 308 layout->StartRowWithPadding( 309 0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing); 310 layout->AddView(title_label); 311 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 312 313 // Confirmation text. 314 views::Label* confirmation = new views::Label(l10n_util::GetStringUTF16( 315 IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_TEXT)); 316 confirmation->SetHorizontalAlignment(gfx::ALIGN_LEFT); 317 confirmation->SetMultiLine(true); 318 confirmation->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 319 ui::ResourceBundle::SmallFont)); 320 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 321 layout->AddView(confirmation); 322 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 323 324 // Confirm and undo buttons. 325 BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET); 326 layout->StartRowWithPadding( 327 0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 328 329 confirm_button_ = new views::LabelButton( 330 this, 331 l10n_util::GetStringUTF16( 332 IDS_MANAGE_PASSWORDS_BLACKLIST_CONFIRMATION_BUTTON)); 333 confirm_button_->SetStyle(views::Button::STYLE_BUTTON); 334 confirm_button_->SetFontList( 335 ui::ResourceBundle::GetSharedInstance().GetFontList( 336 ui::ResourceBundle::SmallFont)); 337 layout->AddView(confirm_button_); 338 339 undo_button_ = 340 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_CANCEL)); 341 undo_button_->SetStyle(views::Button::STYLE_BUTTON); 342 undo_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 343 ui::ResourceBundle::SmallFont)); 344 layout->AddView(undo_button_); 345 346 // Extra padding for visual awesomeness. 347 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 348 349 parent_->set_initially_focused_view(confirm_button_); 350} 351 352ManagePasswordsBubbleView::ConfirmNeverView::~ConfirmNeverView() { 353} 354 355void ManagePasswordsBubbleView::ConfirmNeverView::ButtonPressed( 356 views::Button* sender, 357 const ui::Event& event) { 358 DCHECK(sender == confirm_button_ || sender == undo_button_); 359 if (sender == confirm_button_) 360 parent_->NotifyConfirmedNeverForThisSite(); 361 else 362 parent_->NotifyUndoNeverForThisSite(); 363} 364 365// ManagePasswordsBubbleView::ManageView -------------------------------------- 366 367// A view offering the user a list of her currently saved credentials 368// for the current page, along with a "Manage passwords" link and a 369// "Done" button. 370class ManagePasswordsBubbleView::ManageView : public views::View, 371 public views::ButtonListener, 372 public views::LinkListener { 373 public: 374 explicit ManageView(ManagePasswordsBubbleView* parent); 375 virtual ~ManageView(); 376 377 private: 378 // views::ButtonListener: 379 virtual void ButtonPressed(views::Button* sender, 380 const ui::Event& event) OVERRIDE; 381 382 // views::LinkListener: 383 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; 384 385 ManagePasswordsBubbleView* parent_; 386 387 views::Link* manage_link_; 388 views::LabelButton* done_button_; 389}; 390 391ManagePasswordsBubbleView::ManageView::ManageView( 392 ManagePasswordsBubbleView* parent) 393 : parent_(parent) { 394 views::GridLayout* layout = new views::GridLayout(this); 395 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0)); 396 SetLayoutManager(layout); 397 398 // Add the title. 399 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 400 AddTitleRow(layout, parent_->model()); 401 402 // If we have a list of passwords to store for the current site, display 403 // them to the user for management. Otherwise, render a "No passwords for 404 // this site" message. 405 if (!parent_->model()->best_matches().empty()) { 406 for (autofill::ConstPasswordFormMap::const_iterator i( 407 parent_->model()->best_matches().begin()); 408 i != parent_->model()->best_matches().end(); 409 ++i) { 410 ManagePasswordItemView* item = new ManagePasswordItemView( 411 parent_->model(), 412 *i->second, 413 i == parent_->model()->best_matches().begin() 414 ? password_manager::ui::FIRST_ITEM 415 : password_manager::ui::SUBSEQUENT_ITEM); 416 417 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 418 layout->AddView(item); 419 } 420 } else { 421 views::Label* empty_label = new views::Label( 422 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS)); 423 empty_label->SetMultiLine(true); 424 empty_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 425 empty_label->SetFontList( 426 ui::ResourceBundle::GetSharedInstance().GetFontList( 427 ui::ResourceBundle::SmallFont)); 428 429 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 430 layout->AddView(empty_label); 431 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 432 } 433 434 // Then add the "manage passwords" link and "Done" button. 435 manage_link_ = new views::Link(parent_->model()->manage_link()); 436 manage_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 437 manage_link_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 438 ui::ResourceBundle::SmallFont)); 439 manage_link_->SetUnderline(false); 440 manage_link_->set_listener(this); 441 442 done_button_ = 443 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 444 done_button_->SetStyle(views::Button::STYLE_BUTTON); 445 done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 446 ui::ResourceBundle::SmallFont)); 447 448 BuildColumnSet(layout, LINK_BUTTON_COLUMN_SET); 449 layout->StartRowWithPadding( 450 0, LINK_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 451 layout->AddView(manage_link_); 452 layout->AddView(done_button_); 453 454 // Extra padding for visual awesomeness. 455 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 456 457 parent_->set_initially_focused_view(done_button_); 458} 459 460ManagePasswordsBubbleView::ManageView::~ManageView() { 461} 462 463void ManagePasswordsBubbleView::ManageView::ButtonPressed( 464 views::Button* sender, 465 const ui::Event& event) { 466 DCHECK(sender == done_button_); 467 parent_->model()->OnDoneClicked(); 468 parent_->Close(); 469} 470 471void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link* source, 472 int event_flags) { 473 DCHECK_EQ(source, manage_link_); 474 parent_->model()->OnManageLinkClicked(); 475 parent_->Close(); 476} 477 478// ManagePasswordsBubbleView::BlacklistedView --------------------------------- 479 480// A view offering the user the ability to re-enable the password manager for 481// a specific site after she's decided to "never save passwords". 482class ManagePasswordsBubbleView::BlacklistedView 483 : public views::View, 484 public views::ButtonListener { 485 public: 486 explicit BlacklistedView(ManagePasswordsBubbleView* parent); 487 virtual ~BlacklistedView(); 488 489 private: 490 // views::ButtonListener: 491 virtual void ButtonPressed(views::Button* sender, 492 const ui::Event& event) OVERRIDE; 493 494 ManagePasswordsBubbleView* parent_; 495 496 views::BlueButton* unblacklist_button_; 497 views::LabelButton* done_button_; 498}; 499 500ManagePasswordsBubbleView::BlacklistedView::BlacklistedView( 501 ManagePasswordsBubbleView* parent) 502 : parent_(parent) { 503 views::GridLayout* layout = new views::GridLayout(this); 504 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0)); 505 SetLayoutManager(layout); 506 507 // Add the title. 508 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 509 AddTitleRow(layout, parent_->model()); 510 511 // Add the "Hey! You blacklisted this site!" text. 512 views::Label* blacklisted = new views::Label( 513 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_BLACKLISTED)); 514 blacklisted->SetMultiLine(true); 515 blacklisted->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 516 ui::ResourceBundle::SmallFont)); 517 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 518 layout->AddView(blacklisted); 519 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); 520 521 // Then add the "enable password manager" and "Done" buttons. 522 unblacklist_button_ = new views::BlueButton( 523 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_UNBLACKLIST_BUTTON)); 524 unblacklist_button_->SetFontList( 525 ui::ResourceBundle::GetSharedInstance().GetFontList( 526 ui::ResourceBundle::SmallFont)); 527 done_button_ = 528 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 529 done_button_->SetStyle(views::Button::STYLE_BUTTON); 530 done_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 531 ui::ResourceBundle::SmallFont)); 532 533 BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET); 534 layout->StartRowWithPadding( 535 0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 536 layout->AddView(unblacklist_button_); 537 layout->AddView(done_button_); 538 539 // Extra padding for visual awesomeness. 540 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 541 542 parent_->set_initially_focused_view(unblacklist_button_); 543} 544 545ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() { 546} 547 548void ManagePasswordsBubbleView::BlacklistedView::ButtonPressed( 549 views::Button* sender, 550 const ui::Event& event) { 551 if (sender == done_button_) 552 parent_->model()->OnDoneClicked(); 553 else if (sender == unblacklist_button_) 554 parent_->model()->OnUnblacklistClicked(); 555 else 556 NOTREACHED(); 557 parent_->Close(); 558} 559 560// ManagePasswordsBubbleView::SaveConfirmationView ---------------------------- 561 562// A view confirming to the user that a password was saved and offering a link 563// to the Google account manager. 564class ManagePasswordsBubbleView::SaveConfirmationView 565 : public views::View, 566 public views::ButtonListener, 567 public views::StyledLabelListener { 568 public: 569 explicit SaveConfirmationView(ManagePasswordsBubbleView* parent); 570 virtual ~SaveConfirmationView(); 571 572 private: 573 // views::ButtonListener: 574 virtual void ButtonPressed(views::Button* sender, 575 const ui::Event& event) OVERRIDE; 576 577 // views::StyledLabelListener implementation 578 virtual void StyledLabelLinkClicked(const gfx::Range& range, 579 int event_flags) OVERRIDE; 580 581 ManagePasswordsBubbleView* parent_; 582 583 views::LabelButton* ok_button_; 584}; 585 586ManagePasswordsBubbleView::SaveConfirmationView::SaveConfirmationView( 587 ManagePasswordsBubbleView* parent) 588 : parent_(parent) { 589 views::GridLayout* layout = new views::GridLayout(this); 590 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0)); 591 SetLayoutManager(layout); 592 593 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 594 AddTitleRow(layout, parent_->model()); 595 596 views::StyledLabel* confirmation = 597 new views::StyledLabel(parent_->model()->save_confirmation_text(), this); 598 confirmation->SetBaseFontList( 599 ui::ResourceBundle::GetSharedInstance().GetFontList( 600 ui::ResourceBundle::SmallFont)); 601 confirmation->AddStyleRange( 602 parent_->model()->save_confirmation_link_range(), 603 views::StyledLabel::RangeStyleInfo::CreateForLink()); 604 605 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 606 layout->AddView(confirmation); 607 608 ok_button_ = new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK)); 609 ok_button_->SetStyle(views::Button::STYLE_BUTTON); 610 ok_button_->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 611 ui::ResourceBundle::SmallFont)); 612 613 BuildColumnSet(layout, SINGLE_BUTTON_COLUMN_SET); 614 layout->StartRowWithPadding( 615 0, SINGLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 616 layout->AddView(ok_button_); 617 618 // Extra padding for visual awesomeness. 619 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 620 621 parent_->set_initially_focused_view(ok_button_); 622} 623 624ManagePasswordsBubbleView::SaveConfirmationView::~SaveConfirmationView() { 625} 626 627void ManagePasswordsBubbleView::SaveConfirmationView::StyledLabelLinkClicked( 628 const gfx::Range& range, int event_flags) { 629 DCHECK_EQ(range, parent_->model()->save_confirmation_link_range()); 630 parent_->model()->OnManageLinkClicked(); 631 parent_->Close(); 632} 633 634void ManagePasswordsBubbleView::SaveConfirmationView::ButtonPressed( 635 views::Button* sender, const ui::Event& event) { 636 DCHECK_EQ(sender, ok_button_); 637 parent_->model()->OnOKClicked(); 638 parent_->Close(); 639} 640 641// ManagePasswordsBubbleView::WebContentMouseHandler -------------------------- 642 643// The class listens for WebContentsView events and notifies the bubble if the 644// view was clicked on or received keystrokes. 645class ManagePasswordsBubbleView::WebContentMouseHandler 646 : public ui::EventHandler { 647 public: 648 explicit WebContentMouseHandler(ManagePasswordsBubbleView* bubble); 649 virtual ~WebContentMouseHandler(); 650 651 virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; 652 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; 653 654 private: 655 aura::Window* GetWebContentsWindow(); 656 657 ManagePasswordsBubbleView* bubble_; 658 659 DISALLOW_COPY_AND_ASSIGN(WebContentMouseHandler); 660}; 661 662ManagePasswordsBubbleView::WebContentMouseHandler::WebContentMouseHandler( 663 ManagePasswordsBubbleView* bubble) 664 : bubble_(bubble) { 665 GetWebContentsWindow()->AddPreTargetHandler(this); 666} 667 668ManagePasswordsBubbleView::WebContentMouseHandler::~WebContentMouseHandler() { 669 if (aura::Window* window = GetWebContentsWindow()) 670 window->RemovePreTargetHandler(this); 671} 672 673void ManagePasswordsBubbleView::WebContentMouseHandler::OnKeyEvent( 674 ui::KeyEvent* event) { 675 content::WebContents* web_contents = bubble_->model()->web_contents(); 676 content::RenderViewHost* rvh = web_contents->GetRenderViewHost(); 677 if (rvh->IsFocusedElementEditable() && 678 event->type() == ui::ET_KEY_PRESSED) 679 bubble_->Close(); 680} 681 682void ManagePasswordsBubbleView::WebContentMouseHandler::OnMouseEvent( 683 ui::MouseEvent* event) { 684 if (event->type() == ui::ET_MOUSE_PRESSED) 685 bubble_->Close(); 686} 687 688aura::Window* 689ManagePasswordsBubbleView::WebContentMouseHandler::GetWebContentsWindow() { 690 content::WebContents* web_contents = bubble_->model()->web_contents(); 691 return web_contents ? web_contents->GetNativeView() : NULL; 692} 693 694// ManagePasswordsBubbleView -------------------------------------------------- 695 696// static 697ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ = 698 NULL; 699 700// static 701void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents, 702 DisplayReason reason) { 703 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 704 DCHECK(browser); 705 DCHECK(browser->window()); 706 DCHECK(browser->fullscreen_controller()); 707 708 if (IsShowing()) 709 return; 710 711 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); 712 bool is_fullscreen = browser_view->IsFullscreen(); 713 ManagePasswordsIconView* anchor_view = 714 is_fullscreen 715 ? NULL 716 : browser_view->GetLocationBarView()->manage_passwords_icon_view(); 717 manage_passwords_bubble_ = new ManagePasswordsBubbleView( 718 web_contents, anchor_view, reason); 719 720 if (is_fullscreen) { 721 manage_passwords_bubble_->set_parent_window( 722 web_contents->GetTopLevelNativeWindow()); 723 } 724 725 views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_); 726 727 // Adjust for fullscreen after creation as it relies on the content size. 728 if (is_fullscreen) { 729 manage_passwords_bubble_->AdjustForFullscreen( 730 browser_view->GetBoundsInScreen()); 731 } 732 if (reason == AUTOMATIC) 733 manage_passwords_bubble_->GetWidget()->ShowInactive(); 734 else 735 manage_passwords_bubble_->GetWidget()->Show(); 736} 737 738// static 739void ManagePasswordsBubbleView::CloseBubble() { 740 if (manage_passwords_bubble_) 741 manage_passwords_bubble_->Close(); 742} 743 744// static 745void ManagePasswordsBubbleView::ActivateBubble() { 746 if (!IsShowing()) 747 return; 748 manage_passwords_bubble_->GetWidget()->Activate(); 749} 750 751// static 752bool ManagePasswordsBubbleView::IsShowing() { 753 // The bubble may be in the process of closing. 754 return (manage_passwords_bubble_ != NULL) && 755 manage_passwords_bubble_->GetWidget()->IsVisible(); 756} 757 758content::WebContents* ManagePasswordsBubbleView::web_contents() const { 759 return model()->web_contents(); 760} 761 762ManagePasswordsBubbleView::ManagePasswordsBubbleView( 763 content::WebContents* web_contents, 764 ManagePasswordsIconView* anchor_view, 765 DisplayReason reason) 766 : ManagePasswordsBubble(web_contents, reason), 767 BubbleDelegateView(anchor_view, 768 anchor_view ? views::BubbleBorder::TOP_RIGHT 769 : views::BubbleBorder::NONE), 770 anchor_view_(anchor_view), 771 never_save_passwords_(false), 772 initially_focused_view_(NULL) { 773 // Compensate for built-in vertical padding in the anchor view's image. 774 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 775 if (anchor_view) 776 anchor_view->SetActive(true); 777 mouse_handler_.reset(new WebContentMouseHandler(this)); 778} 779 780ManagePasswordsBubbleView::~ManagePasswordsBubbleView() { 781 if (anchor_view_) 782 anchor_view_->SetActive(false); 783} 784 785void ManagePasswordsBubbleView::AdjustForFullscreen( 786 const gfx::Rect& screen_bounds) { 787 if (GetAnchorView()) 788 return; 789 790 // The bubble's padding from the screen edge, used in fullscreen. 791 const int kFullscreenPaddingEnd = 20; 792 const size_t bubble_half_width = width() / 2; 793 const int x_pos = base::i18n::IsRTL() ? 794 screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd : 795 screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd; 796 SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0)); 797} 798 799void ManagePasswordsBubbleView::Close() { 800 mouse_handler_.reset(); 801 GetWidget()->Close(); 802} 803 804void ManagePasswordsBubbleView::Refresh() { 805 RemoveAllChildViews(true); 806 initially_focused_view_ = NULL; 807 if (password_manager::ui::IsPendingState(model()->state())) { 808 if (never_save_passwords_) 809 AddChildView(new ConfirmNeverView(this)); 810 else 811 AddChildView(new PendingView(this)); 812 } else if (model()->state() == password_manager::ui::BLACKLIST_STATE) { 813 AddChildView(new BlacklistedView(this)); 814 } else if (model()->state() == password_manager::ui::CONFIRMATION_STATE) { 815 AddChildView(new SaveConfirmationView(this)); 816 } else { 817 AddChildView(new ManageView(this)); 818 } 819 GetLayoutManager()->Layout(this); 820} 821 822void ManagePasswordsBubbleView::NotifyNeverForThisSiteClicked() { 823 if (model()->best_matches().empty()) { 824 // Skip confirmation if there are no existing passwords for this site. 825 NotifyConfirmedNeverForThisSite(); 826 } else { 827 never_save_passwords_ = true; 828 Refresh(); 829 } 830} 831 832void ManagePasswordsBubbleView::NotifyConfirmedNeverForThisSite() { 833 model()->OnNeverForThisSiteClicked(); 834 Close(); 835} 836 837void ManagePasswordsBubbleView::NotifyUndoNeverForThisSite() { 838 never_save_passwords_ = false; 839 Refresh(); 840} 841 842void ManagePasswordsBubbleView::Init() { 843 views::FillLayout* layout = new views::FillLayout(); 844 SetLayoutManager(layout); 845 846 Refresh(); 847} 848 849void ManagePasswordsBubbleView::WindowClosing() { 850 // Close() closes the window asynchronously, so by the time we reach here, 851 // |manage_passwords_bubble_| may have already been reset. 852 if (manage_passwords_bubble_ == this) 853 manage_passwords_bubble_ = NULL; 854} 855 856views::View* ManagePasswordsBubbleView::GetInitiallyFocusedView() { 857 return initially_focused_view_; 858} 859