manage_passwords_bubble_view.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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 "base/metrics/histogram.h" 8#include "chrome/browser/chrome_notification_types.h" 9#include "chrome/browser/ui/browser.h" 10#include "chrome/browser/ui/browser_finder.h" 11#include "chrome/browser/ui/browser_window.h" 12#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.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 "content/public/browser/notification_source.h" 18#include "content/public/browser/web_contents_view.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/grid_layout.h" 28#include "ui/views/layout/layout_constants.h" 29 30 31// Helpers -------------------------------------------------------------------- 32 33namespace { 34 35enum FieldType { USERNAME_FIELD, PASSWORD_FIELD }; 36 37// Upper limit on the size of the username and password fields. 38const int kUsernameFieldSize = 30; 39const int kPasswordFieldSize = 22; 40 41// Returns the width of |type| field. 42int GetFieldWidth(FieldType type) { 43 return ui::ResourceBundle::GetSharedInstance() 44 .GetFontList(ui::ResourceBundle::SmallFont) 45 .GetExpectedTextWidth(type == USERNAME_FIELD ? kUsernameFieldSize 46 : kPasswordFieldSize); 47} 48 49class SavePasswordRefusalComboboxModel : public ui::ComboboxModel { 50 public: 51 enum { INDEX_NOPE = 0, INDEX_NEVER_FOR_THIS_SITE = 1, }; 52 53 SavePasswordRefusalComboboxModel() { 54 items_.push_back( 55 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_CANCEL_BUTTON)); 56 items_.push_back( 57 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_BLACKLIST_BUTTON)); 58 } 59 virtual ~SavePasswordRefusalComboboxModel() {} 60 61 private: 62 // Overridden from ui::ComboboxModel: 63 virtual int GetItemCount() const OVERRIDE { return items_.size(); } 64 virtual base::string16 GetItemAt(int index) OVERRIDE { return items_[index]; } 65 virtual bool IsItemSeparatorAt(int index) OVERRIDE { 66 return items_[index].empty(); 67 } 68 virtual int GetDefaultIndex() const OVERRIDE { return 0; } 69 70 std::vector<base::string16> items_; 71 72 DISALLOW_COPY_AND_ASSIGN(SavePasswordRefusalComboboxModel); 73}; 74 75} // namespace 76 77 78// ManagePasswordsBubbleView -------------------------------------------------- 79 80// static 81ManagePasswordsBubbleView* ManagePasswordsBubbleView::manage_passwords_bubble_ = 82 NULL; 83 84// static 85void ManagePasswordsBubbleView::ShowBubble(content::WebContents* web_contents, 86 DisplayReason reason) { 87 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 88 DCHECK(browser); 89 DCHECK(browser->window()); 90 DCHECK(browser->fullscreen_controller()); 91 DCHECK(!IsShowing()); 92 93 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); 94 bool is_fullscreen = browser_view->IsFullscreen(); 95 views::View* anchor_view = is_fullscreen ? 96 NULL : browser_view->GetLocationBarView()->manage_passwords_icon_view(); 97 manage_passwords_bubble_ = new ManagePasswordsBubbleView( 98 web_contents, anchor_view, reason); 99 100 if (is_fullscreen) { 101 manage_passwords_bubble_->set_parent_window( 102 web_contents->GetView()->GetTopLevelNativeWindow()); 103 } 104 105 views::BubbleDelegateView::CreateBubble(manage_passwords_bubble_); 106 107 // Adjust for fullscreen after creation as it relies on the content size. 108 if (is_fullscreen) { 109 manage_passwords_bubble_->AdjustForFullscreen( 110 browser_view->GetBoundsInScreen()); 111 } 112 113 manage_passwords_bubble_->GetWidget()->Show(); 114} 115 116// static 117void ManagePasswordsBubbleView::CloseBubble() { 118 if (manage_passwords_bubble_) 119 manage_passwords_bubble_->CloseWithoutLogging(); 120} 121 122// static 123bool ManagePasswordsBubbleView::IsShowing() { 124 // The bubble may be in the process of closing. 125 return (manage_passwords_bubble_ != NULL) && 126 manage_passwords_bubble_->GetWidget()->IsVisible(); 127} 128 129ManagePasswordsBubbleView::ManagePasswordsBubbleView( 130 content::WebContents* web_contents, 131 views::View* anchor_view, 132 DisplayReason reason) 133 : ManagePasswordsBubble(web_contents, reason), 134 BubbleDelegateView(anchor_view, 135 anchor_view ? views::BubbleBorder::TOP_RIGHT 136 : views::BubbleBorder::NONE) { 137 // Compensate for built-in vertical padding in the anchor view's image. 138 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 139 set_notify_enter_exit_on_child(true); 140} 141 142ManagePasswordsBubbleView::~ManagePasswordsBubbleView() {} 143 144void ManagePasswordsBubbleView::BuildColumnSet(views::GridLayout* layout, 145 ColumnSetType type) { 146 views::ColumnSet* column_set = layout->AddColumnSet(type); 147 column_set->AddPaddingColumn(0, views::kPanelHorizMargin); 148 switch (type) { 149 case SINGLE_VIEW_COLUMN_SET: 150 column_set->AddColumn(views::GridLayout::FILL, 151 views::GridLayout::FILL, 152 0, 153 views::GridLayout::USE_PREF, 154 0, 155 0); 156 break; 157 158 case DOUBLE_BUTTON_COLUMN_SET: 159 column_set->AddColumn(views::GridLayout::TRAILING, 160 views::GridLayout::CENTER, 161 1, 162 views::GridLayout::USE_PREF, 163 0, 164 0); 165 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 166 column_set->AddColumn(views::GridLayout::TRAILING, 167 views::GridLayout::CENTER, 168 0, 169 views::GridLayout::USE_PREF, 170 0, 171 0); 172 break; 173 case LINK_BUTTON_COLUMN_SET: 174 column_set->AddColumn(views::GridLayout::LEADING, 175 views::GridLayout::CENTER, 176 1, 177 views::GridLayout::USE_PREF, 178 0, 179 0); 180 column_set->AddPaddingColumn(0, views::kRelatedButtonHSpacing); 181 column_set->AddColumn(views::GridLayout::TRAILING, 182 views::GridLayout::CENTER, 183 0, 184 views::GridLayout::USE_PREF, 185 0, 186 0); 187 break; 188 } 189 column_set->AddPaddingColumn(0, views::kPanelHorizMargin); 190} 191 192void ManagePasswordsBubbleView::AdjustForFullscreen( 193 const gfx::Rect& screen_bounds) { 194 if (GetAnchorView()) 195 return; 196 197 // The bubble's padding from the screen edge, used in fullscreen. 198 const int kFullscreenPaddingEnd = 20; 199 const size_t bubble_half_width = width() / 2; 200 const int x_pos = base::i18n::IsRTL() ? 201 screen_bounds.x() + bubble_half_width + kFullscreenPaddingEnd : 202 screen_bounds.right() - bubble_half_width - kFullscreenPaddingEnd; 203 SetAnchorRect(gfx::Rect(x_pos, screen_bounds.y(), 0, 0)); 204} 205 206void ManagePasswordsBubbleView::Close() { 207 GetWidget()->Close(); 208} 209 210void ManagePasswordsBubbleView::CloseWithoutLogging() { 211 model()->OnCloseWithoutLogging(); 212 GetWidget()->Close(); 213} 214 215void ManagePasswordsBubbleView::Init() { 216 using views::GridLayout; 217 218 GridLayout* layout = new GridLayout(this); 219 SetFocusable(true); 220 SetLayoutManager(layout); 221 BuildColumnSet(layout, SINGLE_VIEW_COLUMN_SET); 222 BuildColumnSet(layout, DOUBLE_BUTTON_COLUMN_SET); 223 BuildColumnSet(layout, LINK_BUTTON_COLUMN_SET); 224 225 // This calculates the necessary widths for credential columns in the bubble. 226 const int first_field_width = std::max( 227 GetFieldWidth(USERNAME_FIELD), 228 views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED)) 229 .GetPreferredSize() 230 .width()); 231 232 const int second_field_width = std::max( 233 GetFieldWidth(PASSWORD_FIELD), 234 views::Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_UNDO)) 235 .GetPreferredSize() 236 .width()); 237 238 // Build and populate the header. 239 views::Label* title_label = new views::Label(model()->title()); 240 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 241 title_label->SetMultiLine(true); 242 title_label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( 243 ui::ResourceBundle::MediumFont)); 244 245 layout->StartRowWithPadding( 246 0, SINGLE_VIEW_COLUMN_SET, 0, views::kRelatedControlSmallVerticalSpacing); 247 layout->AddView(title_label); 248 layout->AddPaddingRow(0, views::kUnrelatedControlVerticalSpacing); 249 250 if (model()->WaitingToSavePassword()) { 251 // If we've got a password that we're deciding whether or not to save, 252 // then we need to display a single-view columnset containing the 253 // ManagePasswordItemView, followed by double-view columnset containing 254 // a "Save" and "Reject" button. 255 ManagePasswordItemView* item = 256 new ManagePasswordItemView(model(), 257 model()->pending_credentials(), 258 first_field_width, 259 second_field_width, 260 ManagePasswordItemView::FIRST_ITEM); 261 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 262 layout->AddView(item); 263 264 refuse_combobox_ = 265 new views::Combobox(new SavePasswordRefusalComboboxModel()); 266 refuse_combobox_->set_listener(this); 267 refuse_combobox_->SetStyle(views::Combobox::STYLE_ACTION); 268 269 save_button_ = new views::BlueButton( 270 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON)); 271 272 layout->StartRowWithPadding( 273 0, DOUBLE_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 274 layout->AddView(save_button_); 275 layout->AddView(refuse_combobox_); 276 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 277 } else { 278 // If we have a list of passwords to store for the current site, display 279 // them to the user for management. Otherwise, render a "No passwords for 280 // this site" message. 281 // 282 // TODO(mkwst): Do we really want the "No passwords" case? It would probably 283 // be better to only clear the pending password upon navigation, rather than 284 // as soon as the bubble closes. 285 if (!model()->best_matches().empty()) { 286 for (autofill::PasswordFormMap::const_iterator i( 287 model()->best_matches().begin()); 288 i != model()->best_matches().end(); 289 ++i) { 290 ManagePasswordItemView* item = new ManagePasswordItemView( 291 model(), 292 *i->second, 293 first_field_width, 294 second_field_width, 295 i == model()->best_matches().begin() 296 ? ManagePasswordItemView::FIRST_ITEM 297 : ManagePasswordItemView::SUBSEQUENT_ITEM); 298 299 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 300 layout->AddView(item); 301 } 302 } else { 303 views::Label* empty_label = new views::Label( 304 l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_NO_PASSWORDS)); 305 empty_label->SetMultiLine(true); 306 307 layout->StartRow(0, SINGLE_VIEW_COLUMN_SET); 308 layout->AddView(empty_label); 309 } 310 311 // Build a "manage" link and "done" button, and throw them both into a new 312 // row 313 // containing a double-view columnset. 314 manage_link_ = new views::Link(model()->manage_link()); 315 manage_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 316 manage_link_->SetUnderline(false); 317 manage_link_->set_listener(this); 318 319 done_button_ = 320 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 321 done_button_->SetStyle(views::Button::STYLE_BUTTON); 322 323 layout->StartRowWithPadding( 324 0, LINK_BUTTON_COLUMN_SET, 0, views::kRelatedControlVerticalSpacing); 325 layout->AddView(manage_link_); 326 layout->AddView(done_button_); 327 } 328} 329 330void ManagePasswordsBubbleView::WindowClosing() { 331 // Close() closes the window asynchronously, so by the time we reach here, 332 // |manage_passwords_bubble_| may have already been reset. 333 if (manage_passwords_bubble_ == this) 334 manage_passwords_bubble_ = NULL; 335} 336 337void ManagePasswordsBubbleView::ButtonPressed(views::Button* sender, 338 const ui::Event& event) { 339 DCHECK(sender == save_button_ || sender == done_button_); 340 341 if (sender == save_button_) 342 model()->OnSaveClicked(); 343 else 344 model()->OnDoneClicked(); 345 Close(); 346} 347 348void ManagePasswordsBubbleView::LinkClicked(views::Link* source, 349 int event_flags) { 350 DCHECK_EQ(source, manage_link_); 351 model()->OnManageLinkClicked(); 352 Close(); 353} 354 355void ManagePasswordsBubbleView::OnPerformAction(views::Combobox* source) { 356 DCHECK_EQ(source, refuse_combobox_); 357 switch (refuse_combobox_->selected_index()) { 358 case SavePasswordRefusalComboboxModel::INDEX_NOPE: 359 model()->OnNopeClicked(); 360 break; 361 case SavePasswordRefusalComboboxModel::INDEX_NEVER_FOR_THIS_SITE: 362 model()->OnNeverForThisSiteClicked(); 363 break; 364 default: 365 NOTREACHED(); 366 break; 367 } 368 Close(); 369} 370