NaCl docs: add sanitizers to GSoC ideas
[chromium-blink-merge.git] / chrome / browser / ui / views / content_setting_bubble_contents.cc
blob47bd73a027e009e6c543d482b58683a681a6687b
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.
5 #include "chrome/browser/ui/views/content_setting_bubble_contents.h"
7 #include <algorithm>
8 #include <set>
9 #include <string>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/plugins/plugin_finder.h"
16 #include "chrome/browser/plugins/plugin_metadata.h"
17 #include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
18 #include "chrome/browser/ui/content_settings/content_setting_media_menu_model.h"
19 #include "chrome/browser/ui/views/browser_dialogs.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/content_settings/core/browser/host_content_settings_map.h"
22 #include "content/public/browser/plugin_service.h"
23 #include "content/public/browser/web_contents.h"
24 #include "ui/base/cursor/cursor.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/gfx/text_utils.h"
30 #include "ui/views/controls/button/label_button.h"
31 #include "ui/views/controls/button/menu_button.h"
32 #include "ui/views/controls/button/radio_button.h"
33 #include "ui/views/controls/image_view.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/controls/link.h"
36 #include "ui/views/controls/menu/menu.h"
37 #include "ui/views/controls/menu/menu_config.h"
38 #include "ui/views/controls/menu/menu_runner.h"
39 #include "ui/views/controls/separator.h"
40 #include "ui/views/layout/grid_layout.h"
41 #include "ui/views/layout/layout_constants.h"
42 #include "ui/views/native_cursor.h"
44 namespace {
46 // If we don't clamp the maximum width, then very long URLs and titles can make
47 // the bubble arbitrarily wide.
48 const int kMaxContentsWidth = 500;
50 // When we have multiline labels, we should set a minimum width lest we get very
51 // narrow bubbles with lots of line-wrapping.
52 const int kMinMultiLineContentsWidth = 250;
54 // The minimum width of the media menu buttons.
55 const int kMinMediaMenuButtonWidth = 150;
57 } // namespace
59 using content::PluginService;
60 using content::WebContents;
63 // ContentSettingBubbleContents::Favicon --------------------------------------
65 class ContentSettingBubbleContents::Favicon : public views::ImageView {
66 public:
67 Favicon(const gfx::Image& image,
68 ContentSettingBubbleContents* parent,
69 views::Link* link);
70 ~Favicon() override;
72 private:
73 // views::View overrides:
74 bool OnMousePressed(const ui::MouseEvent& event) override;
75 void OnMouseReleased(const ui::MouseEvent& event) override;
76 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override;
78 ContentSettingBubbleContents* parent_;
79 views::Link* link_;
82 ContentSettingBubbleContents::Favicon::Favicon(
83 const gfx::Image& image,
84 ContentSettingBubbleContents* parent,
85 views::Link* link)
86 : parent_(parent),
87 link_(link) {
88 SetImage(image.AsImageSkia());
91 ContentSettingBubbleContents::Favicon::~Favicon() {
94 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
95 const ui::MouseEvent& event) {
96 return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
99 void ContentSettingBubbleContents::Favicon::OnMouseReleased(
100 const ui::MouseEvent& event) {
101 if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
102 HitTestPoint(event.location())) {
103 parent_->LinkClicked(link_, event.flags());
107 gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
108 const ui::MouseEvent& event) {
109 return views::GetNativeHandCursor();
113 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
115 struct ContentSettingBubbleContents::MediaMenuParts {
116 explicit MediaMenuParts(content::MediaStreamType type);
117 ~MediaMenuParts();
119 content::MediaStreamType type;
120 scoped_ptr<ui::SimpleMenuModel> menu_model;
122 private:
123 DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
126 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
127 content::MediaStreamType type)
128 : type(type) {}
130 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
132 // ContentSettingBubbleContents -----------------------------------------------
134 ContentSettingBubbleContents::ContentSettingBubbleContents(
135 ContentSettingBubbleModel* content_setting_bubble_model,
136 content::WebContents* web_contents,
137 views::View* anchor_view,
138 views::BubbleBorder::Arrow arrow)
139 : content::WebContentsObserver(web_contents),
140 BubbleDelegateView(anchor_view, arrow),
141 content_setting_bubble_model_(content_setting_bubble_model),
142 custom_link_(NULL),
143 manage_link_(NULL),
144 learn_more_link_(NULL),
145 close_button_(NULL) {
146 // Compensate for built-in vertical padding in the anchor view's image.
147 set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
150 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
151 STLDeleteValues(&media_menus_);
154 gfx::Size ContentSettingBubbleContents::GetPreferredSize() const {
155 gfx::Size preferred_size(views::View::GetPreferredSize());
156 int preferred_width =
157 (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
158 (kMinMultiLineContentsWidth > preferred_size.width())) ?
159 kMinMultiLineContentsWidth : preferred_size.width();
160 preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
161 return preferred_size;
164 void ContentSettingBubbleContents::UpdateMenuLabel(
165 content::MediaStreamType type,
166 const std::string& label) {
167 for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
168 it != media_menus_.end(); ++it) {
169 if (it->second->type == type) {
170 it->first->SetText(base::UTF8ToUTF16(label));
171 return;
174 NOTREACHED();
177 void ContentSettingBubbleContents::Init() {
178 using views::GridLayout;
180 GridLayout* layout = new views::GridLayout(this);
181 SetLayoutManager(layout);
183 const int kSingleColumnSetId = 0;
184 views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
185 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
186 GridLayout::USE_PREF, 0, 0);
187 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
188 column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
189 GridLayout::USE_PREF, 0, 0);
191 const ContentSettingBubbleModel::BubbleContent& bubble_content =
192 content_setting_bubble_model_->bubble_content();
193 bool bubble_content_empty = true;
195 if (!bubble_content.title.empty()) {
196 views::Label* title_label = new views::Label(base::UTF8ToUTF16(
197 bubble_content.title));
198 title_label->SetMultiLine(true);
199 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
200 layout->StartRow(0, kSingleColumnSetId);
201 layout->AddView(title_label);
202 bubble_content_empty = false;
205 if (!bubble_content.learn_more_link.empty()) {
206 learn_more_link_ =
207 new views::Link(base::UTF8ToUTF16(bubble_content.learn_more_link));
208 learn_more_link_->set_listener(this);
209 learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
210 layout->AddView(learn_more_link_);
211 bubble_content_empty = false;
214 if (!bubble_content.plugin_names.empty()) {
215 const int kPluginsColumnSetId = 4;
216 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
217 views::ColumnSet* plugins_column_set =
218 layout->AddColumnSet(kPluginsColumnSetId);
219 plugins_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
220 GridLayout::USE_PREF, 0, 0);
221 plugins_column_set->AddPaddingColumn(
222 0, views::kRelatedControlHorizontalSpacing);
223 plugins_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
224 GridLayout::USE_PREF, 0, 0);
226 views::Label* plugin_names_label =
227 new views::Label(bubble_content.plugin_names);
228 plugin_names_label->SetMultiLine(true);
229 plugin_names_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
230 layout->StartRow(0, kPluginsColumnSetId);
231 layout->AddView(plugin_names_label);
232 bubble_content_empty = false;
235 if (content_setting_bubble_model_->content_type() ==
236 CONTENT_SETTINGS_TYPE_POPUPS) {
237 const int kPopupColumnSetId = 2;
238 views::ColumnSet* popup_column_set =
239 layout->AddColumnSet(kPopupColumnSetId);
240 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
241 GridLayout::USE_PREF, 0, 0);
242 popup_column_set->AddPaddingColumn(
243 0, views::kRelatedControlHorizontalSpacing);
244 popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
245 GridLayout::USE_PREF, 0, 0);
247 for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
248 i(bubble_content.popup_items.begin());
249 i != bubble_content.popup_items.end(); ++i) {
250 if (!bubble_content_empty)
251 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
252 layout->StartRow(0, kPopupColumnSetId);
254 views::Link* link = new views::Link(base::UTF8ToUTF16(i->title));
255 link->set_listener(this);
256 link->SetElideBehavior(gfx::ELIDE_MIDDLE);
257 popup_links_[link] = i - bubble_content.popup_items.begin();
258 layout->AddView(new Favicon(i->image, this, link));
259 layout->AddView(link);
260 bubble_content_empty = false;
264 const int indented_kSingleColumnSetId = 3;
265 // Insert a column set with greater indent.
266 views::ColumnSet* indented_single_column_set =
267 layout->AddColumnSet(indented_kSingleColumnSetId);
268 indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
269 indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
270 1, GridLayout::USE_PREF, 0, 0);
272 const ContentSettingBubbleModel::RadioGroup& radio_group =
273 bubble_content.radio_group;
274 if (!radio_group.radio_items.empty()) {
275 if (!bubble_content_empty)
276 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
277 for (ContentSettingBubbleModel::RadioItems::const_iterator i(
278 radio_group.radio_items.begin());
279 i != radio_group.radio_items.end(); ++i) {
280 views::RadioButton* radio =
281 new views::RadioButton(base::UTF8ToUTF16(*i), 0);
282 radio->SetEnabled(bubble_content.radio_group_enabled);
283 radio->set_listener(this);
284 radio_group_.push_back(radio);
285 layout->StartRow(0, indented_kSingleColumnSetId);
286 layout->AddView(radio);
287 bubble_content_empty = false;
289 DCHECK(!radio_group_.empty());
290 // Now that the buttons have been added to the view hierarchy, it's safe
291 // to call SetChecked() on them.
292 radio_group_[radio_group.default_item]->SetChecked(true);
295 // Layout code for the media device menus.
296 if (content_setting_bubble_model_->content_type() ==
297 CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
298 const int kMediaMenuColumnSetId = 2;
299 views::ColumnSet* menu_column_set =
300 layout->AddColumnSet(kMediaMenuColumnSetId);
301 menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
302 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
303 GridLayout::USE_PREF, 0, 0);
304 menu_column_set->AddPaddingColumn(
305 0, views::kRelatedControlHorizontalSpacing);
306 menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
307 GridLayout::USE_PREF, 0, 0);
309 for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
310 bubble_content.media_menus.begin());
311 i != bubble_content.media_menus.end(); ++i) {
312 if (!bubble_content_empty)
313 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
314 layout->StartRow(0, kMediaMenuColumnSetId);
316 views::Label* label =
317 new views::Label(base::UTF8ToUTF16(i->second.label));
318 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
320 views::MenuButton* menu_button = new views::MenuButton(
321 NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
322 this, true);
323 menu_button->SetStyle(views::Button::STYLE_BUTTON);
324 menu_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
325 menu_button->set_animate_on_state_change(false);
327 MediaMenuParts* menu_view = new MediaMenuParts(i->first);
328 menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
329 i->first,
330 content_setting_bubble_model_.get(),
331 base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
332 base::Unretained(this))));
333 media_menus_[menu_button] = menu_view;
335 if (!menu_view->menu_model->GetItemCount()) {
336 // Show a "None available" title and grey out the menu when there are
337 // no available devices.
338 menu_button->SetText(
339 l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
340 menu_button->SetEnabled(false);
343 // Disable the device selection when the website is managing the devices
344 // itself.
345 if (i->second.disabled)
346 menu_button->SetEnabled(false);
348 layout->AddView(label);
349 layout->AddView(menu_button);
351 bubble_content_empty = false;
355 UpdateMenuButtonSizes(GetNativeTheme());
357 const gfx::FontList& domain_font =
358 ui::ResourceBundle::GetSharedInstance().GetFontList(
359 ui::ResourceBundle::BoldFont);
360 for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
361 bubble_content.domain_lists.begin());
362 i != bubble_content.domain_lists.end(); ++i) {
363 layout->StartRow(0, kSingleColumnSetId);
364 views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title));
365 section_title->SetMultiLine(true);
366 section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
367 layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
368 for (std::set<std::string>::const_iterator j = i->hosts.begin();
369 j != i->hosts.end(); ++j) {
370 layout->StartRow(0, indented_kSingleColumnSetId);
371 layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font));
373 bubble_content_empty = false;
376 if (!bubble_content.custom_link.empty()) {
377 custom_link_ =
378 new views::Link(base::UTF8ToUTF16(bubble_content.custom_link));
379 custom_link_->SetEnabled(bubble_content.custom_link_enabled);
380 custom_link_->set_listener(this);
381 if (!bubble_content_empty)
382 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
383 layout->StartRow(0, kSingleColumnSetId);
384 layout->AddView(custom_link_);
385 bubble_content_empty = false;
388 const int kDoubleColumnSetId = 1;
389 views::ColumnSet* double_column_set =
390 layout->AddColumnSet(kDoubleColumnSetId);
391 if (!bubble_content_empty) {
392 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
393 layout->StartRow(0, kSingleColumnSetId);
394 layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
395 GridLayout::FILL, GridLayout::FILL);
396 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
399 double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
400 GridLayout::USE_PREF, 0, 0);
401 double_column_set->AddPaddingColumn(
402 0, views::kUnrelatedControlHorizontalSpacing);
403 double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
404 GridLayout::USE_PREF, 0, 0);
406 layout->StartRow(0, kDoubleColumnSetId);
407 manage_link_ =
408 new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
409 manage_link_->set_listener(this);
410 layout->AddView(manage_link_);
412 close_button_ =
413 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
414 close_button_->SetStyle(views::Button::STYLE_BUTTON);
415 layout->AddView(close_button_);
418 void ContentSettingBubbleContents::DidNavigateMainFrame(
419 const content::LoadCommittedDetails& details,
420 const content::FrameNavigateParams& params) {
421 // Content settings are based on the main frame, so if it switches then
422 // close up shop.
423 content_setting_bubble_model_->OnDoneClicked();
424 GetWidget()->Close();
427 void ContentSettingBubbleContents::OnNativeThemeChanged(
428 const ui::NativeTheme* theme) {
429 views::BubbleDelegateView::OnNativeThemeChanged(theme);
430 UpdateMenuButtonSizes(theme);
433 void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
434 const ui::Event& event) {
435 RadioGroup::const_iterator i(
436 std::find(radio_group_.begin(), radio_group_.end(), sender));
437 if (i != radio_group_.end()) {
438 content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
439 return;
441 DCHECK_EQ(sender, close_button_);
442 content_setting_bubble_model_->OnDoneClicked();
443 GetWidget()->Close();
446 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
447 int event_flags) {
448 if (source == learn_more_link_) {
449 content_setting_bubble_model_->OnLearnMoreLinkClicked();
450 GetWidget()->Close();
451 return;
453 if (source == custom_link_) {
454 content_setting_bubble_model_->OnCustomLinkClicked();
455 GetWidget()->Close();
456 return;
458 if (source == manage_link_) {
459 GetWidget()->Close();
460 content_setting_bubble_model_->OnManageLinkClicked();
461 // CAREFUL: Showing the settings window activates it, which deactivates the
462 // info bubble, which causes it to close, which deletes us.
463 return;
466 PopupLinks::const_iterator i(popup_links_.find(source));
467 DCHECK(i != popup_links_.end());
468 content_setting_bubble_model_->OnPopupClicked(i->second);
471 void ContentSettingBubbleContents::OnMenuButtonClicked(
472 views::View* source,
473 const gfx::Point& point) {
474 MediaMenuPartsMap::iterator j(media_menus_.find(
475 static_cast<views::MenuButton*>(source)));
476 DCHECK(j != media_menus_.end());
477 menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get(),
478 views::MenuRunner::HAS_MNEMONICS));
480 gfx::Point screen_location;
481 views::View::ConvertPointToScreen(j->first, &screen_location);
482 ignore_result(
483 menu_runner_->RunMenuAt(source->GetWidget(),
484 j->first,
485 gfx::Rect(screen_location, j->first->size()),
486 views::MENU_ANCHOR_TOPLEFT,
487 ui::MENU_SOURCE_NONE));
490 void ContentSettingBubbleContents::UpdateMenuButtonSizes(
491 const ui::NativeTheme* theme) {
492 const views::MenuConfig config = views::MenuConfig(theme);
493 const int margins = config.item_left_margin + config.check_width +
494 config.label_to_arrow_padding + config.arrow_width +
495 config.arrow_to_edge_padding;
497 // The preferred media menu size sort of copies the logic in
498 // MenuItemView::CalculateDimensions(). When this was using TextButton, it
499 // completely coincidentally matched the logic in MenuItemView. We now need
500 // to redo this manually.
501 int menu_width = 0;
502 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
503 i != media_menus_.end(); ++i) {
504 for (int j = 0; j < i->second->menu_model->GetItemCount(); ++j) {
505 int string_width = gfx::GetStringWidth(
506 i->second->menu_model->GetLabelAt(j),
507 config.font_list);
509 menu_width = std::max(menu_width, string_width);
513 // Make sure the width is at least kMinMediaMenuButtonWidth. The
514 // maximum width will be clamped by kMaxContentsWidth of the view.
515 menu_width = std::max(kMinMediaMenuButtonWidth, menu_width + margins);
517 for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
518 i != media_menus_.end(); ++i) {
519 i->first->SetMinSize(gfx::Size(menu_width, 0));
520 i->first->SetMaxSize(gfx::Size(menu_width, 0));