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.
5 #include "chrome/browser/ui/views/passwords/manage_passwords_bubble_view.h"
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"
32 // Helpers --------------------------------------------------------------------
36 const int kDesiredBubbleWidth = 370;
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,
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,
49 // | | (LEADING, CENTER) | | (TRAILING, CENTER) | |
50 // Used for buttons at the bottom of the bubble which should occupy
52 LINK_BUTTON_COLUMN_SET = 2,
55 // Construct an appropriate ColumnSet for the given |type|, and add it
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);
62 case SINGLE_VIEW_COLUMN_SET:
63 column_set->AddColumn(views::GridLayout::FILL,
64 views::GridLayout::FILL,
66 views::GridLayout::FIXED,
71 case DOUBLE_BUTTON_COLUMN_SET:
72 column_set->AddColumn(views::GridLayout::TRAILING,
73 views::GridLayout::CENTER,
75 views::GridLayout::USE_PREF,
78 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
79 column_set->AddColumn(views::GridLayout::TRAILING,
80 views::GridLayout::CENTER,
82 views::GridLayout::USE_PREF,
86 case LINK_BUTTON_COLUMN_SET:
87 column_set->AddColumn(views::GridLayout::LEADING,
88 views::GridLayout::CENTER,
90 views::GridLayout::USE_PREF,
93 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing);
94 column_set->AddColumn(views::GridLayout::TRAILING,
95 views::GridLayout::CENTER,
97 views::GridLayout::USE_PREF,
102 column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
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));
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);
124 // Globals --------------------------------------------------------------------
128 void ShowManagePasswordsBubble(content::WebContents* web_contents) {
129 ManagePasswordsUIController* controller =
130 ManagePasswordsUIController::FromWebContents(web_contents);
131 ManagePasswordsBubbleView::ShowBubble(
133 controller->state() ==
134 password_manager::ui::PENDING_PASSWORD_AND_BUBBLE_STATE
135 ? ManagePasswordsBubbleView::AUTOMATIC
136 : ManagePasswordsBubbleView::USER_ACTION);
139 } // namespace chrome
142 // ManagePasswordsBubbleView::PendingView -------------------------------------
144 ManagePasswordsBubbleView::PendingView::PendingView(
145 ManagePasswordsBubbleView* parent)
147 views::GridLayout* layout = new views::GridLayout(this);
148 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
149 SetLayoutManager(layout);
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));
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.
168 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
169 AddTitleRow(layout, parent_->model());
172 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
173 layout->AddView(item);
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());
182 // Extra padding for visual awesomeness.
183 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
186 ManagePasswordsBubbleView::PendingView::~PendingView() {
189 void ManagePasswordsBubbleView::PendingView::ButtonPressed(
190 views::Button* sender,
191 const ui::Event& event) {
192 DCHECK(sender == save_button_);
193 parent_->model()->OnSaveClicked();
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();
205 case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE:
206 parent_->NotifyNeverForThisSiteClicked();
211 // ManagePasswordsBubbleView::ConfirmNeverView ---------------------------------
213 ManagePasswordsBubbleView::ConfirmNeverView::ConfirmNeverView(
214 ManagePasswordsBubbleView* parent)
216 views::GridLayout* layout = new views::GridLayout(this);
217 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
218 SetLayoutManager(layout);
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);
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);
244 // Confirm and undo buttons.
245 BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET);
246 layout->StartRowWithPadding(
247 0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing);
249 confirm_button_ = new views::LabelButton(
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_);
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_);
266 // Extra padding for visual awesomeness.
267 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
270 ManagePasswordsBubbleView::ConfirmNeverView::~ConfirmNeverView() {
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();
280 parent_->NotifyUndoNeverForThisSite();
283 // ManagePasswordsBubbleView::ManageView --------------------------------------
285 ManagePasswordsBubbleView::ManageView::ManageView(
286 ManagePasswordsBubbleView* parent)
288 views::GridLayout* layout = new views::GridLayout(this);
289 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
290 SetLayoutManager(layout);
293 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
294 AddTitleRow(layout, parent_->model());
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();
304 ManagePasswordItemView* item = new ManagePasswordItemView(
307 i == parent_->model()->best_matches().begin()
308 ? ManagePasswordItemView::FIRST_ITEM
309 : ManagePasswordItemView::SUBSEQUENT_ITEM);
311 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
312 layout->AddView(item);
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));
323 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET);
324 layout->AddView(empty_label);
325 layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing);
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);
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));
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_);
348 // Extra padding for visual awesomeness.
349 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
352 ManagePasswordsBubbleView::ManageView::~ManageView() {
355 void ManagePasswordsBubbleView::ManageView::ButtonPressed(
356 views::Button* sender,
357 const ui::Event& event) {
358 DCHECK(sender == done_button_);
359 parent_->model()->OnDoneClicked();
363 void ManagePasswordsBubbleView::ManageView::LinkClicked(views::Link* source,
365 DCHECK_EQ(source, manage_link_);
366 parent_->model()->OnManageLinkClicked();
370 // ManagePasswordsBubbleView::BlacklistedView ---------------------------------
372 ManagePasswordsBubbleView::BlacklistedView::BlacklistedView(
373 ManagePasswordsBubbleView* parent)
375 views::GridLayout* layout = new views::GridLayout(this);
376 layout->set_minimum_size(gfx::Size(kDesiredBubbleWidth, 0));
377 SetLayoutManager(layout);
380 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET);
381 AddTitleRow(layout, parent_->model());
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);
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));
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));
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_);
411 // Extra padding for visual awesomeness.
412 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
415 ManagePasswordsBubbleView::BlacklistedView::~BlacklistedView() {
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();
430 // ManagePasswordsBubbleView --------------------------------------------------
433 ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ =
437 void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents,
438 DisplayReason reason) {
439 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
441 DCHECK(browser->window());
442 DCHECK(browser->fullscreen_controller());
447 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
448 bool is_fullscreen = browser_view->IsFullscreen();
449 ManagePasswordsIconView* anchor_view =
452 : browser_view->GetLocationBarView()->manage_passwords_icon_view();
453 manage_passwords_bubble_ = new ManagePasswordsBubbleView(
454 web_contents, anchor_view, reason);
457 manage_passwords_bubble_->set_parent_window(
458 web_contents->GetTopLevelNativeWindow());
461 views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_);
463 // Adjust for fullscreen after creation as it relies on the content size.
465 manage_passwords_bubble_->AdjustForFullscreen(
466 browser_view->GetBoundsInScreen());
468 manage_passwords_bubble_->GetWidget()->Show();
469 manage_passwords_bubble_->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
473 void ManagePasswordsBubbleView::CloseBubble() {
474 if (manage_passwords_bubble_)
475 manage_passwords_bubble_->Close();
479 bool ManagePasswordsBubbleView::IsShowing() {
480 // The bubble may be in the process of closing.
481 return (manage_passwords_bubble_ != NULL) &&
482 manage_passwords_bubble_->GetWidget()->IsVisible();
485 ManagePasswordsBubbleView::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);
499 anchor_view->SetActive(true);
502 ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {
504 anchor_view_->SetActive(false);
507 void ManagePasswordsBubbleView::AdjustForFullscreen(
508 const gfx::Rect& screen_bounds) {
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));
521 void ManagePasswordsBubbleView::Close() {
522 GetWidget()->Close();
525 void ManagePasswordsBubbleView::Init() {
526 views::FillLayout* layout = new views::FillLayout();
527 SetLayoutManager(layout);
533 void 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;
540 void ManagePasswordsBubbleView::Refresh() {
541 RemoveAllChildViews(true);
542 if (password_manager::ui::IsPendingState(model()->state())) {
543 if (never_save_passwords_)
544 AddChildView(new ConfirmNeverView(this));
546 AddChildView(new PendingView(this));
547 } else if (model()->state() == password_manager::ui::BLACKLIST_STATE) {
548 AddChildView(new BlacklistedView(this));
550 AddChildView(new ManageView(this));
552 GetLayoutManager()->Layout(this);
555 void ManagePasswordsBubbleView::NotifyNeverForThisSiteClicked() {
556 if (model()->best_matches().empty()) {
557 // Skip confirmation if there are no existing passwords for this site.
558 NotifyConfirmedNeverForThisSite();
560 never_save_passwords_ = true;
565 void ManagePasswordsBubbleView::NotifyConfirmedNeverForThisSite() {
566 model()->OnNeverForThisSiteClicked();
570 void ManagePasswordsBubbleView::NotifyUndoNeverForThisSite() {
571 never_save_passwords_ = false;