content_setting_bubble_contents.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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/plugin_service.h" 22#include "content/public/browser/web_contents.h" 23#include "grit/generated_resources.h" 24#include "grit/theme_resources.h" 25#include "ui/base/l10n/l10n_util.h" 26#include "ui/base/models/simple_menu_model.h" 27#include "ui/base/resource/resource_bundle.h" 28#include "ui/gfx/font_list.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// ContentSettingBubbleContents ----------------------------------------------- 139 140ContentSettingBubbleContents::ContentSettingBubbleContents( 141 ContentSettingBubbleModel* content_setting_bubble_model, 142 content::WebContents* web_contents, 143 views::View* anchor_view, 144 views::BubbleBorder::Arrow arrow) 145 : content::WebContentsObserver(web_contents), 146 BubbleDelegateView(anchor_view, arrow), 147 content_setting_bubble_model_(content_setting_bubble_model), 148 custom_link_(NULL), 149 manage_link_(NULL), 150 close_button_(NULL) { 151 // Compensate for built-in vertical padding in the anchor view's image. 152 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0)); 153} 154 155ContentSettingBubbleContents::~ContentSettingBubbleContents() { 156 STLDeleteValues(&media_menus_); 157} 158 159gfx::Size ContentSettingBubbleContents::GetPreferredSize() const { 160 gfx::Size preferred_size(views::View::GetPreferredSize()); 161 int preferred_width = 162 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() && 163 (kMinMultiLineContentsWidth > preferred_size.width())) ? 164 kMinMultiLineContentsWidth : preferred_size.width(); 165 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth)); 166 return preferred_size; 167} 168 169void ContentSettingBubbleContents::UpdateMenuLabel( 170 content::MediaStreamType type, 171 const std::string& label) { 172 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin(); 173 it != media_menus_.end(); ++it) { 174 if (it->second->type == type) { 175 it->first->SetText(base::UTF8ToUTF16(label)); 176 return; 177 } 178 } 179 NOTREACHED(); 180} 181 182void ContentSettingBubbleContents::Init() { 183 using views::GridLayout; 184 185 GridLayout* layout = new views::GridLayout(this); 186 SetLayoutManager(layout); 187 188 const int kSingleColumnSetId = 0; 189 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId); 190 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 191 GridLayout::USE_PREF, 0, 0); 192 193 const ContentSettingBubbleModel::BubbleContent& bubble_content = 194 content_setting_bubble_model_->bubble_content(); 195 bool bubble_content_empty = true; 196 197 if (!bubble_content.title.empty()) { 198 views::Label* title_label = new views::Label(base::UTF8ToUTF16( 199 bubble_content.title)); 200 title_label->SetMultiLine(true); 201 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 202 layout->StartRow(0, kSingleColumnSetId); 203 layout->AddView(title_label); 204 bubble_content_empty = false; 205 } 206 207 if (content_setting_bubble_model_->content_type() == 208 CONTENT_SETTINGS_TYPE_POPUPS) { 209 const int kPopupColumnSetId = 2; 210 views::ColumnSet* popup_column_set = 211 layout->AddColumnSet(kPopupColumnSetId); 212 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 213 GridLayout::USE_PREF, 0, 0); 214 popup_column_set->AddPaddingColumn( 215 0, views::kRelatedControlHorizontalSpacing); 216 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 217 GridLayout::USE_PREF, 0, 0); 218 219 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator 220 i(bubble_content.popup_items.begin()); 221 i != bubble_content.popup_items.end(); ++i) { 222 if (!bubble_content_empty) 223 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 224 layout->StartRow(0, kPopupColumnSetId); 225 226 views::Link* link = new views::Link(base::UTF8ToUTF16(i->title)); 227 link->set_listener(this); 228 link->SetElideBehavior(gfx::ELIDE_MIDDLE); 229 popup_links_[link] = i - bubble_content.popup_items.begin(); 230 layout->AddView(new Favicon(i->image, this, link)); 231 layout->AddView(link); 232 bubble_content_empty = false; 233 } 234 } 235 236 const int indented_kSingleColumnSetId = 3; 237 // Insert a column set with greater indent. 238 views::ColumnSet* indented_single_column_set = 239 layout->AddColumnSet(indented_kSingleColumnSetId); 240 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 241 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 242 1, GridLayout::USE_PREF, 0, 0); 243 244 const ContentSettingBubbleModel::RadioGroup& radio_group = 245 bubble_content.radio_group; 246 if (!radio_group.radio_items.empty()) { 247 if (!bubble_content_empty) 248 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 249 for (ContentSettingBubbleModel::RadioItems::const_iterator i( 250 radio_group.radio_items.begin()); 251 i != radio_group.radio_items.end(); ++i) { 252 views::RadioButton* radio = 253 new views::RadioButton(base::UTF8ToUTF16(*i), 0); 254 radio->SetEnabled(bubble_content.radio_group_enabled); 255 radio->set_listener(this); 256 radio_group_.push_back(radio); 257 layout->StartRow(0, indented_kSingleColumnSetId); 258 layout->AddView(radio); 259 bubble_content_empty = false; 260 } 261 DCHECK(!radio_group_.empty()); 262 // Now that the buttons have been added to the view hierarchy, it's safe 263 // to call SetChecked() on them. 264 radio_group_[radio_group.default_item]->SetChecked(true); 265 } 266 267 // Layout code for the media device menus. 268 if (content_setting_bubble_model_->content_type() == 269 CONTENT_SETTINGS_TYPE_MEDIASTREAM) { 270 const int kMediaMenuColumnSetId = 2; 271 views::ColumnSet* menu_column_set = 272 layout->AddColumnSet(kMediaMenuColumnSetId); 273 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent); 274 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0, 275 GridLayout::USE_PREF, 0, 0); 276 menu_column_set->AddPaddingColumn( 277 0, views::kRelatedControlHorizontalSpacing); 278 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1, 279 GridLayout::USE_PREF, 0, 0); 280 281 int menu_width = 0; 282 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i( 283 bubble_content.media_menus.begin()); 284 i != bubble_content.media_menus.end(); ++i) { 285 if (!bubble_content_empty) 286 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 287 layout->StartRow(0, kMediaMenuColumnSetId); 288 289 views::Label* label = 290 new views::Label(base::UTF8ToUTF16(i->second.label)); 291 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 292 293 views::MenuButton* menu_button = new views::MenuButton( 294 NULL, base::UTF8ToUTF16((i->second.selected_device.name)), 295 this, true); 296 menu_button->SetHorizontalAlignment(gfx::ALIGN_LEFT); 297 menu_button->set_animate_on_state_change(false); 298 299 MediaMenuParts* menu_view = new MediaMenuParts(i->first); 300 menu_view->menu_model.reset(new ContentSettingMediaMenuModel( 301 i->first, 302 content_setting_bubble_model_.get(), 303 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel, 304 base::Unretained(this)))); 305 media_menus_[menu_button] = menu_view; 306 307 if (!menu_view->menu_model->GetItemCount()) { 308 // Show a "None available" title and grey out the menu when there are 309 // no available devices. 310 menu_button->SetText( 311 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE)); 312 menu_button->SetEnabled(false); 313 } 314 315 // Disable the device selection when the website is managing the devices 316 // itself. 317 if (i->second.disabled) 318 menu_button->SetEnabled(false); 319 320 // Use the longest width of the menus as the width of the menu buttons. 321 menu_width = std::max(menu_width, 322 GetPreferredMediaMenuWidth( 323 menu_button, menu_view->menu_model.get())); 324 325 layout->AddView(label); 326 layout->AddView(menu_button); 327 328 bubble_content_empty = false; 329 } 330 331 // Make sure the width is at least kMinMediaMenuButtonWidth. The 332 // maximum width will be clamped by kMaxContentsWidth of the view. 333 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width); 334 335 // Set all the menu buttons to the width we calculated above. 336 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin(); 337 i != media_menus_.end(); ++i) { 338 i->first->set_min_size(gfx::Size(menu_width, 0)); 339 i->first->set_max_size(gfx::Size(menu_width, 0)); 340 } 341 } 342 343 const gfx::FontList& domain_font = 344 ui::ResourceBundle::GetSharedInstance().GetFontList( 345 ui::ResourceBundle::BoldFont); 346 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i( 347 bubble_content.domain_lists.begin()); 348 i != bubble_content.domain_lists.end(); ++i) { 349 layout->StartRow(0, kSingleColumnSetId); 350 views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title)); 351 section_title->SetMultiLine(true); 352 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT); 353 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING); 354 for (std::set<std::string>::const_iterator j = i->hosts.begin(); 355 j != i->hosts.end(); ++j) { 356 layout->StartRow(0, indented_kSingleColumnSetId); 357 layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font)); 358 } 359 bubble_content_empty = false; 360 } 361 362 if (!bubble_content.custom_link.empty()) { 363 custom_link_ = 364 new views::Link(base::UTF8ToUTF16(bubble_content.custom_link)); 365 custom_link_->SetEnabled(bubble_content.custom_link_enabled); 366 custom_link_->set_listener(this); 367 if (!bubble_content_empty) 368 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 369 layout->StartRow(0, kSingleColumnSetId); 370 layout->AddView(custom_link_); 371 bubble_content_empty = false; 372 } 373 374 const int kDoubleColumnSetId = 1; 375 views::ColumnSet* double_column_set = 376 layout->AddColumnSet(kDoubleColumnSetId); 377 if (!bubble_content_empty) { 378 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 379 layout->StartRow(0, kSingleColumnSetId); 380 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1, 381 GridLayout::FILL, GridLayout::FILL); 382 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 383 } 384 385 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1, 386 GridLayout::USE_PREF, 0, 0); 387 double_column_set->AddPaddingColumn( 388 0, views::kUnrelatedControlHorizontalSpacing); 389 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0, 390 GridLayout::USE_PREF, 0, 0); 391 392 layout->StartRow(0, kDoubleColumnSetId); 393 manage_link_ = 394 new views::Link(base::UTF8ToUTF16(bubble_content.manage_link)); 395 manage_link_->set_listener(this); 396 layout->AddView(manage_link_); 397 398 close_button_ = 399 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE)); 400 close_button_->SetStyle(views::Button::STYLE_BUTTON); 401 layout->AddView(close_button_); 402} 403 404void ContentSettingBubbleContents::DidNavigateMainFrame( 405 const content::LoadCommittedDetails& details, 406 const content::FrameNavigateParams& params) { 407 // Content settings are based on the main frame, so if it switches then 408 // close up shop. 409 content_setting_bubble_model_->OnDoneClicked(); 410 GetWidget()->Close(); 411} 412 413void ContentSettingBubbleContents::ButtonPressed(views::Button* sender, 414 const ui::Event& event) { 415 RadioGroup::const_iterator i( 416 std::find(radio_group_.begin(), radio_group_.end(), sender)); 417 if (i != radio_group_.end()) { 418 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin()); 419 return; 420 } 421 DCHECK_EQ(sender, close_button_); 422 content_setting_bubble_model_->OnDoneClicked(); 423 GetWidget()->Close(); 424} 425 426void ContentSettingBubbleContents::LinkClicked(views::Link* source, 427 int event_flags) { 428 if (source == custom_link_) { 429 content_setting_bubble_model_->OnCustomLinkClicked(); 430 GetWidget()->Close(); 431 return; 432 } 433 if (source == manage_link_) { 434 GetWidget()->Close(); 435 content_setting_bubble_model_->OnManageLinkClicked(); 436 // CAREFUL: Showing the settings window activates it, which deactivates the 437 // info bubble, which causes it to close, which deletes us. 438 return; 439 } 440 441 PopupLinks::const_iterator i(popup_links_.find(source)); 442 DCHECK(i != popup_links_.end()); 443 content_setting_bubble_model_->OnPopupClicked(i->second); 444} 445 446void ContentSettingBubbleContents::OnMenuButtonClicked( 447 views::View* source, 448 const gfx::Point& point) { 449 MediaMenuPartsMap::iterator j(media_menus_.find( 450 static_cast<views::MenuButton*>(source))); 451 DCHECK(j != media_menus_.end()); 452 menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get())); 453 454 gfx::Point screen_location; 455 views::View::ConvertPointToScreen(j->first, &screen_location); 456 ignore_result( 457 menu_runner_->RunMenuAt(source->GetWidget(), 458 j->first, 459 gfx::Rect(screen_location, j->first->size()), 460 views::MENU_ANCHOR_TOPLEFT, 461 ui::MENU_SOURCE_NONE, 462 views::MenuRunner::HAS_MNEMONICS)); 463} 464 465int ContentSettingBubbleContents::GetPreferredMediaMenuWidth( 466 views::MenuButton* button, 467 ui::SimpleMenuModel* menu_model) { 468 base::string16 title = button->GetText(); 469 470 int width = button->GetPreferredSize().width(); 471 for (int i = 0; i < menu_model->GetItemCount(); ++i) { 472 button->SetText(menu_model->GetLabelAt(i)); 473 width = std::max(width, button->GetPreferredSize().width()); 474 } 475 476 // Recover the title for the menu button. 477 button->SetText(title); 478 return width; 479} 480