content_setting_bubble_contents.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
1// Copyright (c) 2012 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/content_setting_bubble_contents.h" 6 7#include <algorithm> 8#include <set> 9#include <string> 10#include <vector> 11 12#include "base/bind.h" 13#include "base/stl_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "chrome/browser/content_settings/host_content_settings_map.h" 16#include "chrome/browser/plugins/plugin_finder.h" 17#include "chrome/browser/plugins/plugin_metadata.h" 18#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h" 19#include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h" 20#include "chrome/browser/ui/views/browser_dialogs.h" 21#include "content/public/browser/notification_source.h" 22#include "content/public/browser/notification_types.h" 23#include "content/public/browser/plugin_service.h" 24#include "content/public/browser/web_contents.h" 25#include "grit/generated_resources.h" 26#include "grit/theme_resources.h" 27#include "ui/base/l10n/l10n_util.h" 28#include "ui/base/models/simple_menu_model.h" 29#include "ui/views/controls/button/label_button.h" 30#include "ui/views/controls/button/menu_button.h" 31#include "ui/views/controls/button/radio_button.h" 32#include "ui/views/controls/image_view.h" 33#include "ui/views/controls/label.h" 34#include "ui/views/controls/link.h" 35#include "ui/views/controls/menu/menu.h" 36#include "ui/views/controls/menu/menu_runner.h" 37#include "ui/views/controls/separator.h" 38#include "ui/views/layout/grid_layout.h" 39#include "ui/views/layout/layout_constants.h" 40 41#if defined(USE_AURA) 42#include "ui/base/cursor/cursor.h" 43#endif 44 45namespace { 46 47// If we don't clamp the maximum width, then very long URLs and titles can make 48// the bubble arbitrarily wide. 49const int kMaxContentsWidth = 500; 50 51// When we have multiline labels, we should set a minimum width lest we get very 52// narrow bubbles with lots of line-wrapping. 53const int kMinMultiLineContentsWidth = 250; 54 55// The minimum width of the media menu buttons. 56const int kMinMediaMenuButtonWidth = 100; 57 58} // namespace 59 60using content::PluginService; 61using content::WebContents; 62 63 64// ContentSettingBubbleContents::Favicon -------------------------------------- 65 66class ContentSettingBubbleContents::Favicon : public views::ImageView { 67 public: 68 Favicon(const gfx::Image& image, 69 ContentSettingBubbleContents* parent, 70 views::Link* link); 71 virtual ~Favicon(); 72 73 private: 74 // views::View overrides: 75 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE; 76 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE; 77 virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE; 78 79 ContentSettingBubbleContents* parent_; 80 views::Link* link_; 81}; 82 83ContentSettingBubbleContents::Favicon::Favicon( 84 const gfx::Image& image, 85 ContentSettingBubbleContents* parent, 86 views::Link* link) 87 : parent_(parent), 88 link_(link) { 89 SetImage(image.AsImageSkia()); 90} 91 92ContentSettingBubbleContents::Favicon::~Favicon() { 93} 94 95bool ContentSettingBubbleContents::Favicon::OnMousePressed( 96 const ui::MouseEvent& event) { 97 return event.IsLeftMouseButton() || event.IsMiddleMouseButton(); 98} 99 100void ContentSettingBubbleContents::Favicon::OnMouseReleased( 101 const ui::MouseEvent& event) { 102 if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) && 103 HitTestPoint(event.location())) { 104 parent_->LinkClicked(link_, event.flags()); 105 } 106} 107 108gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor( 109 const ui::MouseEvent& event) { 110#if defined(USE_AURA) 111 return ui::kCursorHand; 112#elif defined(OS_WIN) 113 static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND); 114 return g_hand_cursor; 115#endif 116} 117 118 119// ContentSettingBubbleContents::MediaMenuParts ------------------------------- 120 121struct ContentSettingBubbleContents::MediaMenuParts { 122 explicit MediaMenuParts(content::MediaStreamType type); 123 ~MediaMenuParts(); 124 125 content::MediaStreamType type; 126 scoped_ptr<ui::SimpleMenuModel> menu_model; 127 128 private: 129 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts); 130}; 131 132ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts( 133 content::MediaStreamType type) 134 : type(type) {} 135 136ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {} 137 138 139// ContentSettingBubbleContents ----------------------------------------------- 140 141ContentSettingBubbleContents::ContentSettingBubbleContents( 142 ContentSettingBubbleModel* content_setting_bubble_model, 143 WebContents* web_contents, 144 views::View* anchor_view, 145 views::BubbleBorder::Arrow arrow) 146 : BubbleDelegateView(anchor_view, arrow), 147 content_setting_bubble_model_(content_setting_bubble_model), 148 web_contents_(web_contents), 149 cancel_button_(NULL), 150 save_button_(NULL), 151 custom_link_(NULL), 152 manage_link_(NULL), 153 close_button_(NULL) { 154 // Compensate for built-in vertical padding in the anchor view's image. 155 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 156 157 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 158 content::Source<WebContents>(web_contents)); 159} 160 161ContentSettingBubbleContents::~ContentSettingBubbleContents() { 162 STLDeleteValues(&media_menus_); 163} 164 165gfx::Size ContentSettingBubbleContents::GetPreferredSize() { 166 gfx::Size preferred_size(views::View::GetPreferredSize()); 167 int preferred_width = 168 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() && 169 (kMinMultiLineContentsWidth > preferred_size.width())) ? 170 kMinMultiLineContentsWidth : preferred_size.width(); 171 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth)); 172 return preferred_size; 173} 174 175void ContentSettingBubbleContents::UpdateMenuLabel( 176 content::MediaStreamType type, 177 const std::string& label) { 178 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin(); 179 it != media_menus_.end(); ++it) { 180 if (it->second->type == type) { 181 it->first->SetText(UTF8ToUTF16(label)); 182 return; 183 } 184 } 185 NOTREACHED(); 186} 187 188void ContentSettingBubbleContents::Init() { 189 using views::GridLayout; 190 191 GridLayout* layout = new views::GridLayout(this); 192 SetLayoutManager(layout); 193 194 const int kSingleColumnSetId = 0; 195 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId); 196 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 197 GridLayout::USE_PREF, 0, 0); 198 199 const ContentSettingBubbleModel::BubbleContent& bubble_content = 200 content_setting_bubble_model_->bubble_content(); 201 bool bubble_content_empty = true; 202 203 if (!bubble_content.title.empty()) { 204 views::Label* title_label = new views::Label(UTF8ToUTF16( 205 bubble_content.title)); 206 title_label->SetMultiLine(true); 207 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 208 layout->StartRow(0, kSingleColumnSetId); 209 layout->AddView(title_label); 210 bubble_content_empty = false; 211 } 212 213 const std::set<std::string>& plugins = bubble_content.resource_identifiers; 214 if (!plugins.empty()) { 215 if (!bubble_content_empty) 216 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 217 PluginFinder* finder = PluginFinder::GetInstance(); 218 for (std::set<std::string>::const_iterator i(plugins.begin()); 219 i != plugins.end(); ++i) { 220 string16 name = finder->FindPluginNameWithIdentifier(*i); 221 layout->StartRow(0, kSingleColumnSetId); 222 layout->AddView(new views::Label(name)); 223 bubble_content_empty = false; 224 } 225 } 226 227 if (content_setting_bubble_model_->content_type() == 228 CONTENT_SETTINGS_TYPE_POPUPS) { 229 const int kPopupColumnSetId = 2; 230 views::ColumnSet* popup_column_set = 231 layout->AddColumnSet(kPopupColumnSetId); 232 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 233 GridLayout::USE_PREF, 0, 0); 234 popup_column_set->AddPaddingColumn( 235 0, views::kRelatedControlHorizontalSpacing); 236 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 237 GridLayout::USE_PREF, 0, 0); 238 239 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator 240 i(bubble_content.popup_items.begin()); 241 i != bubble_content.popup_items.end(); ++i) { 242 if (!bubble_content_empty) 243 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 244 layout->StartRow(0, kPopupColumnSetId); 245 246 views::Link* link = new views::Link(UTF8ToUTF16(i->title)); 247 link->set_listener(this); 248 link->SetElideBehavior(views::Label::ELIDE_IN_MIDDLE); 249 popup_links_[link] = i - bubble_content.popup_items.begin(); 250 layout->AddView(new Favicon(i->image, this, link)); 251 layout->AddView(link); 252 bubble_content_empty = false; 253 } 254 } 255 256 const int indented_kSingleColumnSetId = 3; 257 // Insert a column set with greater indent. 258 views::ColumnSet* indented_single_column_set = 259 layout->AddColumnSet(indented_kSingleColumnSetId); 260 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 261 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 262 1, GridLayout::USE_PREF, 0, 0); 263 264 const ContentSettingBubbleModel::RadioGroup& radio_group = 265 bubble_content.radio_group; 266 if (!radio_group.radio_items.empty()) { 267 if (!bubble_content_empty) 268 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 269 for (ContentSettingBubbleModel::RadioItems::const_iterator i( 270 radio_group.radio_items.begin()); 271 i != radio_group.radio_items.end(); ++i) { 272 views::RadioButton* radio = new views::RadioButton(UTF8ToUTF16(*i), 0); 273 radio->SetEnabled(bubble_content.radio_group_enabled); 274 radio->set_listener(this); 275 radio_group_.push_back(radio); 276 layout->StartRow(0, indented_kSingleColumnSetId); 277 layout->AddView(radio); 278 bubble_content_empty = false; 279 } 280 DCHECK(!radio_group_.empty()); 281 // Now that the buttons have been added to the view hierarchy, it's safe 282 // to call SetChecked() on them. 283 radio_group_[radio_group.default_item]->SetChecked(true); 284 } 285 286 // Layout code for the media device menus. 287 if (content_setting_bubble_model_->content_type() == 288 CONTENT_SETTINGS_TYPE_MEDIASTREAM) { 289 const int kMediaMenuColumnSetId = 2; 290 views::ColumnSet* menu_column_set = 291 layout->AddColumnSet(kMediaMenuColumnSetId); 292 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 293 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 294 GridLayout::USE_PREF, 0, 0); 295 menu_column_set->AddPaddingColumn( 296 0, views::kRelatedControlHorizontalSpacing); 297 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 298 GridLayout::USE_PREF, 0, 0); 299 300 int menu_width = 0; 301 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i( 302 bubble_content.media_menus.begin()); 303 i != bubble_content.media_menus.end(); ++i) { 304 if (!bubble_content_empty) 305 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 306 layout->StartRow(0, kMediaMenuColumnSetId); 307 308 views::Label* label = new views::Label(UTF8ToUTF16(i->second.label)); 309 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 310 311 views::MenuButton* menu_button = new views::MenuButton( 312 NULL, UTF8ToUTF16((i->second.selected_device.name)), this, true); 313 menu_button->set_alignment(views::TextButton::ALIGN_LEFT); 314 menu_button->set_border( 315 new views::TextButtonNativeThemeBorder(menu_button)); 316 menu_button->set_animate_on_state_change(false); 317 318 MediaMenuParts* menu_view = new MediaMenuParts(i->first); 319 menu_view->menu_model.reset(new ContentSettingMediaMenuModel( 320 i->first, 321 content_setting_bubble_model_.get(), 322 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel, 323 base::Unretained(this)))); 324 media_menus_[menu_button] = menu_view; 325 326 if (!menu_view->menu_model->GetItemCount()) { 327 // Show a "None available" title and grey out the menu when there is no 328 // available device. 329 menu_button->SetText( 330 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE)); 331 menu_button->SetEnabled(false); 332 } 333 334 // Use the longest width of the menus as the width of the menu buttons. 335 menu_width = std::max(menu_width, 336 GetPreferredMediaMenuWidth( 337 menu_button, menu_view->menu_model.get())); 338 339 layout->AddView(label); 340 layout->AddView(menu_button); 341 342 bubble_content_empty = false; 343 } 344 345 // Make sure the width is at least kMinMediaMenuButtonWidth. The 346 // maximum width will be clamped by kMaxContentsWidth of the view. 347 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width); 348 349 // Set all the menu buttons to the width we calculated above. 350 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin(); 351 i != media_menus_.end(); ++i) { 352 i->first->set_min_width(menu_width); 353 i->first->set_max_width(menu_width); 354 } 355 } 356 357 gfx::Font domain_font = 358 views::Label().font().DeriveFont(0, gfx::Font::BOLD); 359 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i( 360 bubble_content.domain_lists.begin()); 361 i != bubble_content.domain_lists.end(); ++i) { 362 layout->StartRow(0, kSingleColumnSetId); 363 views::Label* section_title = new views::Label(UTF8ToUTF16(i->title)); 364 section_title->SetMultiLine(true); 365 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT); 366 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING); 367 for (std::set<std::string>::const_iterator j = i->hosts.begin(); 368 j != i->hosts.end(); ++j) { 369 layout->StartRow(0, indented_kSingleColumnSetId); 370 layout->AddView(new views::Label(UTF8ToUTF16(*j), domain_font)); 371 } 372 bubble_content_empty = false; 373 } 374 375 if (!bubble_content.custom_link.empty()) { 376 custom_link_ = new views::Link(UTF8ToUTF16(bubble_content.custom_link)); 377 custom_link_->SetEnabled(bubble_content.custom_link_enabled); 378 custom_link_->set_listener(this); 379 if (!bubble_content_empty) 380 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 381 layout->StartRow(0, kSingleColumnSetId); 382 layout->AddView(custom_link_); 383 bubble_content_empty = false; 384 } 385 386 const int kDoubleColumnSetId = 1; 387 views::ColumnSet* double_column_set = 388 layout->AddColumnSet(kDoubleColumnSetId); 389 if (content_setting_bubble_model_->content_type() == 390 CONTENT_SETTINGS_TYPE_SAVE_PASSWORD) { 391 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 1, 392 GridLayout::USE_PREF, 0, 0); 393 double_column_set->AddPaddingColumn( 394 0, views::kRelatedControlSmallVerticalSpacing); 395 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, 396 GridLayout::USE_PREF, 0, 0); 397 398 const int kSingleColumnRightSetId = 2; 399 views::ColumnSet* right_column_set = 400 layout->AddColumnSet(kSingleColumnRightSetId); 401 right_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 402 GridLayout::USE_PREF, 0, 0); 403 404 cancel_button_ = new views::LabelButton( 405 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_BLACKLIST_BUTTON)); 406 cancel_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 407 save_button_ = new views::LabelButton( 408 this, l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON)); 409 save_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 410 manage_link_ = new views::Link(UTF8ToUTF16(bubble_content.manage_link)); 411 manage_link_->set_listener(this); 412 413 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 414 415 layout->StartRow(0, kDoubleColumnSetId); 416 layout->AddView(cancel_button_); 417 layout->AddView(save_button_); 418 419 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 420 layout->StartRow(0, kSingleColumnSetId); 421 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1, 422 GridLayout::FILL, GridLayout::FILL); 423 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 424 425 layout->StartRow(0, kSingleColumnRightSetId); 426 layout->AddView(manage_link_); 427 } else { 428 if (!bubble_content_empty) { 429 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 430 layout->StartRow(0, kSingleColumnSetId); 431 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1, 432 GridLayout::FILL, GridLayout::FILL); 433 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 434 } 435 436 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, 437 GridLayout::USE_PREF, 0, 0); 438 double_column_set->AddPaddingColumn( 439 0, views::kUnrelatedControlHorizontalSpacing); 440 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, 441 GridLayout::USE_PREF, 0, 0); 442 443 layout->StartRow(0, kDoubleColumnSetId); 444 manage_link_ = new views::Link(UTF8ToUTF16(bubble_content.manage_link)); 445 manage_link_->set_listener(this); 446 layout->AddView(manage_link_); 447 448 close_button_ = 449 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 450 close_button_->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON); 451 layout->AddView(close_button_); 452 } 453} 454 455void ContentSettingBubbleContents::ButtonPressed(views::Button* sender, 456 const ui::Event& event) { 457 RadioGroup::const_iterator i( 458 std::find(radio_group_.begin(), radio_group_.end(), sender)); 459 if (i != radio_group_.end()) { 460 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin()); 461 return; 462 } 463 464 if (sender == save_button_) 465 content_setting_bubble_model_->OnSaveClicked(); 466 else if (sender == cancel_button_) 467 content_setting_bubble_model_->OnCancelClicked(); 468 else if (sender == close_button_) 469 content_setting_bubble_model_->OnDoneClicked(); 470 else 471 NOTREACHED(); 472 StartFade(false); 473} 474 475void ContentSettingBubbleContents::LinkClicked(views::Link* source, 476 int event_flags) { 477 if (source == custom_link_) { 478 content_setting_bubble_model_->OnCustomLinkClicked(); 479 StartFade(false); 480 return; 481 } 482 if (source == manage_link_) { 483 StartFade(false); 484 content_setting_bubble_model_->OnManageLinkClicked(); 485 // CAREFUL: Showing the settings window activates it, which deactivates the 486 // info bubble, which causes it to close, which deletes us. 487 return; 488 } 489 490 PopupLinks::const_iterator i(popup_links_.find(source)); 491 DCHECK(i != popup_links_.end()); 492 content_setting_bubble_model_->OnPopupClicked(i->second); 493} 494 495void ContentSettingBubbleContents::OnMenuButtonClicked( 496 views::View* source, 497 const gfx::Point& point) { 498 MediaMenuPartsMap::iterator i(media_menus_.find( 499 static_cast<views::MenuButton*>(source))); 500 DCHECK(i != media_menus_.end()); 501 502 menu_runner_.reset(new views::MenuRunner(i->second->menu_model.get())); 503 504 gfx::Point screen_location; 505 views::View::ConvertPointToScreen(i->first, &screen_location); 506 ignore_result(menu_runner_->RunMenuAt( 507 source->GetWidget(), 508 i->first, 509 gfx::Rect(screen_location, i->first->size()), 510 views::MenuItemView::TOPLEFT, 511 ui::MENU_SOURCE_NONE, 512 views::MenuRunner::HAS_MNEMONICS)); 513} 514 515void ContentSettingBubbleContents::Observe( 516 int type, 517 const content::NotificationSource& source, 518 const content::NotificationDetails& details) { 519 DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type); 520 DCHECK(source == content::Source<WebContents>(web_contents_)); 521 web_contents_ = NULL; 522} 523 524int ContentSettingBubbleContents::GetPreferredMediaMenuWidth( 525 views::MenuButton* button, 526 ui::SimpleMenuModel* menu_model) { 527 string16 title = button->text(); 528 529 int width = button->GetPreferredSize().width(); 530 for (int i = 0; i < menu_model->GetItemCount(); ++i) { 531 button->SetText(menu_model->GetLabelAt(i)); 532 width = std::max(width, button->GetPreferredSize().width()); 533 } 534 535 // Recover the title for the menu button. 536 button->SetText(title); 537 return width; 538} 539