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