Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / content_setting_bubble_contents.cc
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/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
43 #if defined(USE_AURA)
44 #include "ui/base/cursor/cursor.h"
45 #endif
46
47 namespace {
48
49 // If we don't clamp the maximum width, then very long URLs and titles can make
50 // the bubble arbitrarily wide.
51 const int kMaxContentsWidth = 500;
52
53 // When we have multiline labels, we should set a minimum width lest we get very
54 // narrow bubbles with lots of line-wrapping.
55 const int kMinMultiLineContentsWidth = 250;
56
57 // The minimum width of the media menu buttons.
58 const int kMinMediaMenuButtonWidth = 150;
59
60 }  // namespace
61
62 using content::PluginService;
63 using content::WebContents;
64
65
66 // ContentSettingBubbleContents::Favicon --------------------------------------
67
68 class ContentSettingBubbleContents::Favicon : public views::ImageView {
69  public:
70   Favicon(const gfx::Image& image,
71           ContentSettingBubbleContents* parent,
72           views::Link* link);
73   virtual ~Favicon();
74
75  private:
76   // views::View overrides:
77   virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE;
78   virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE;
79   virtual gfx::NativeCursor GetCursor(const ui::MouseEvent& event) OVERRIDE;
80
81   ContentSettingBubbleContents* parent_;
82   views::Link* link_;
83 };
84
85 ContentSettingBubbleContents::Favicon::Favicon(
86     const gfx::Image& image,
87     ContentSettingBubbleContents* parent,
88     views::Link* link)
89     : parent_(parent),
90       link_(link) {
91   SetImage(image.AsImageSkia());
92 }
93
94 ContentSettingBubbleContents::Favicon::~Favicon() {
95 }
96
97 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
98     const ui::MouseEvent& event) {
99   return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
100 }
101
102 void ContentSettingBubbleContents::Favicon::OnMouseReleased(
103     const ui::MouseEvent& event) {
104   if ((event.IsLeftMouseButton() || event.IsMiddleMouseButton()) &&
105      HitTestPoint(event.location())) {
106     parent_->LinkClicked(link_, event.flags());
107   }
108 }
109
110 gfx::NativeCursor ContentSettingBubbleContents::Favicon::GetCursor(
111     const ui::MouseEvent& event) {
112 #if defined(USE_AURA)
113   return ui::kCursorHand;
114 #elif defined(OS_WIN)
115   static HCURSOR g_hand_cursor = LoadCursor(NULL, IDC_HAND);
116   return g_hand_cursor;
117 #endif
118 }
119
120
121 // ContentSettingBubbleContents::MediaMenuParts -------------------------------
122
123 struct ContentSettingBubbleContents::MediaMenuParts {
124   explicit MediaMenuParts(content::MediaStreamType type);
125   ~MediaMenuParts();
126
127   content::MediaStreamType type;
128   scoped_ptr<ui::SimpleMenuModel> menu_model;
129
130  private:
131   DISALLOW_COPY_AND_ASSIGN(MediaMenuParts);
132 };
133
134 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
135     content::MediaStreamType type)
136     : type(type) {}
137
138 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
139
140 // ContentSettingBubbleContents -----------------------------------------------
141
142 ContentSettingBubbleContents::ContentSettingBubbleContents(
143     ContentSettingBubbleModel* content_setting_bubble_model,
144     content::WebContents* web_contents,
145     views::View* anchor_view,
146     views::BubbleBorder::Arrow arrow)
147     : content::WebContentsObserver(web_contents),
148       BubbleDelegateView(anchor_view, arrow),
149       content_setting_bubble_model_(content_setting_bubble_model),
150       custom_link_(NULL),
151       manage_link_(NULL),
152       learn_more_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
158 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
159   STLDeleteValues(&media_menus_);
160 }
161
162 gfx::Size ContentSettingBubbleContents::GetPreferredSize() const {
163   gfx::Size preferred_size(views::View::GetPreferredSize());
164   int preferred_width =
165       (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
166        (kMinMultiLineContentsWidth > preferred_size.width())) ?
167       kMinMultiLineContentsWidth : preferred_size.width();
168   preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
169   return preferred_size;
170 }
171
172 void ContentSettingBubbleContents::UpdateMenuLabel(
173     content::MediaStreamType type,
174     const std::string& label) {
175   for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
176        it != media_menus_.end(); ++it) {
177     if (it->second->type == type) {
178       it->first->SetText(base::UTF8ToUTF16(label));
179       return;
180     }
181   }
182   NOTREACHED();
183 }
184
185 void ContentSettingBubbleContents::Init() {
186   using views::GridLayout;
187
188   GridLayout* layout = new views::GridLayout(this);
189   SetLayoutManager(layout);
190
191   const int kSingleColumnSetId = 0;
192   views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
193   column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
194                         GridLayout::USE_PREF, 0, 0);
195   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
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(base::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   if (!bubble_content.learn_more_link.empty()) {
214     learn_more_link_ =
215         new views::Link(base::UTF8ToUTF16(bubble_content.learn_more_link));
216     learn_more_link_->set_listener(this);
217     learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
218     layout->AddView(learn_more_link_);
219     bubble_content_empty = false;
220   }
221
222   if (content_setting_bubble_model_->content_type() ==
223       CONTENT_SETTINGS_TYPE_POPUPS) {
224     const int kPopupColumnSetId = 2;
225     views::ColumnSet* popup_column_set =
226         layout->AddColumnSet(kPopupColumnSetId);
227     popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
228                                 GridLayout::USE_PREF, 0, 0);
229     popup_column_set->AddPaddingColumn(
230         0, views::kRelatedControlHorizontalSpacing);
231     popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
232                                 GridLayout::USE_PREF, 0, 0);
233
234     for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
235          i(bubble_content.popup_items.begin());
236          i != bubble_content.popup_items.end(); ++i) {
237       if (!bubble_content_empty)
238         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
239       layout->StartRow(0, kPopupColumnSetId);
240
241       views::Link* link = new views::Link(base::UTF8ToUTF16(i->title));
242       link->set_listener(this);
243       link->SetElideBehavior(gfx::ELIDE_MIDDLE);
244       popup_links_[link] = i - bubble_content.popup_items.begin();
245       layout->AddView(new Favicon(i->image, this, link));
246       layout->AddView(link);
247       bubble_content_empty = false;
248     }
249   }
250
251   const int indented_kSingleColumnSetId = 3;
252   // Insert a column set with greater indent.
253   views::ColumnSet* indented_single_column_set =
254       layout->AddColumnSet(indented_kSingleColumnSetId);
255   indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
256   indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
257                                         1, GridLayout::USE_PREF, 0, 0);
258
259   const ContentSettingBubbleModel::RadioGroup& radio_group =
260       bubble_content.radio_group;
261   if (!radio_group.radio_items.empty()) {
262     if (!bubble_content_empty)
263       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
264     for (ContentSettingBubbleModel::RadioItems::const_iterator i(
265          radio_group.radio_items.begin());
266          i != radio_group.radio_items.end(); ++i) {
267       views::RadioButton* radio =
268           new views::RadioButton(base::UTF8ToUTF16(*i), 0);
269       radio->SetEnabled(bubble_content.radio_group_enabled);
270       radio->set_listener(this);
271       radio_group_.push_back(radio);
272       layout->StartRow(0, indented_kSingleColumnSetId);
273       layout->AddView(radio);
274       bubble_content_empty = false;
275     }
276     DCHECK(!radio_group_.empty());
277     // Now that the buttons have been added to the view hierarchy, it's safe
278     // to call SetChecked() on them.
279     radio_group_[radio_group.default_item]->SetChecked(true);
280   }
281
282   // Layout code for the media device menus.
283   if (content_setting_bubble_model_->content_type() ==
284       CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
285     const int kMediaMenuColumnSetId = 2;
286     views::ColumnSet* menu_column_set =
287         layout->AddColumnSet(kMediaMenuColumnSetId);
288     menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
289     menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
290                                GridLayout::USE_PREF, 0, 0);
291     menu_column_set->AddPaddingColumn(
292         0, views::kRelatedControlHorizontalSpacing);
293     menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
294                                GridLayout::USE_PREF, 0, 0);
295
296     for (ContentSettingBubbleModel::MediaMenuMap::const_iterator i(
297          bubble_content.media_menus.begin());
298          i != bubble_content.media_menus.end(); ++i) {
299       if (!bubble_content_empty)
300         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
301       layout->StartRow(0, kMediaMenuColumnSetId);
302
303       views::Label* label =
304           new views::Label(base::UTF8ToUTF16(i->second.label));
305       label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
306
307       views::MenuButton* menu_button = new views::MenuButton(
308           NULL, base::UTF8ToUTF16((i->second.selected_device.name)),
309           this, true);
310       menu_button->SetStyle(views::Button::STYLE_BUTTON);
311       menu_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
312       menu_button->set_animate_on_state_change(false);
313
314       MediaMenuParts* menu_view = new MediaMenuParts(i->first);
315       menu_view->menu_model.reset(new ContentSettingMediaMenuModel(
316           i->first,
317           content_setting_bubble_model_.get(),
318           base::Bind(&ContentSettingBubbleContents::UpdateMenuLabel,
319                      base::Unretained(this))));
320       media_menus_[menu_button] = menu_view;
321
322       if (!menu_view->menu_model->GetItemCount()) {
323         // Show a "None available" title and grey out the menu when there are
324         // no available devices.
325         menu_button->SetText(
326             l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE));
327         menu_button->SetEnabled(false);
328       }
329
330       // Disable the device selection when the website is managing the devices
331       // itself.
332       if (i->second.disabled)
333         menu_button->SetEnabled(false);
334
335       layout->AddView(label);
336       layout->AddView(menu_button);
337
338       bubble_content_empty = false;
339     }
340   }
341
342   UpdateMenuButtonSizes(GetNativeTheme());
343
344   const gfx::FontList& domain_font =
345       ui::ResourceBundle::GetSharedInstance().GetFontList(
346           ui::ResourceBundle::BoldFont);
347   for (std::vector<ContentSettingBubbleModel::DomainList>::const_iterator i(
348            bubble_content.domain_lists.begin());
349        i != bubble_content.domain_lists.end(); ++i) {
350     layout->StartRow(0, kSingleColumnSetId);
351     views::Label* section_title = new views::Label(base::UTF8ToUTF16(i->title));
352     section_title->SetMultiLine(true);
353     section_title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
354     layout->AddView(section_title, 1, 1, GridLayout::FILL, GridLayout::LEADING);
355     for (std::set<std::string>::const_iterator j = i->hosts.begin();
356          j != i->hosts.end(); ++j) {
357       layout->StartRow(0, indented_kSingleColumnSetId);
358       layout->AddView(new views::Label(base::UTF8ToUTF16(*j), domain_font));
359     }
360     bubble_content_empty = false;
361   }
362
363   if (!bubble_content.custom_link.empty()) {
364     custom_link_ =
365         new views::Link(base::UTF8ToUTF16(bubble_content.custom_link));
366     custom_link_->SetEnabled(bubble_content.custom_link_enabled);
367     custom_link_->set_listener(this);
368     if (!bubble_content_empty)
369       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
370     layout->StartRow(0, kSingleColumnSetId);
371     layout->AddView(custom_link_);
372     bubble_content_empty = false;
373   }
374
375   const int kDoubleColumnSetId = 1;
376   views::ColumnSet* double_column_set =
377       layout->AddColumnSet(kDoubleColumnSetId);
378   if (!bubble_content_empty) {
379       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
380       layout->StartRow(0, kSingleColumnSetId);
381       layout->AddView(new views::Separator(views::Separator::HORIZONTAL), 1, 1,
382                       GridLayout::FILL, GridLayout::FILL);
383       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
384     }
385
386     double_column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 1,
387                                  GridLayout::USE_PREF, 0, 0);
388     double_column_set->AddPaddingColumn(
389         0, views::kUnrelatedControlHorizontalSpacing);
390     double_column_set->AddColumn(GridLayout::TRAILING, GridLayout::CENTER, 0,
391                                  GridLayout::USE_PREF, 0, 0);
392
393     layout->StartRow(0, kDoubleColumnSetId);
394     manage_link_ =
395         new views::Link(base::UTF8ToUTF16(bubble_content.manage_link));
396     manage_link_->set_listener(this);
397     layout->AddView(manage_link_);
398
399     close_button_ =
400         new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_DONE));
401     close_button_->SetStyle(views::Button::STYLE_BUTTON);
402     layout->AddView(close_button_);
403 }
404
405 void ContentSettingBubbleContents::DidNavigateMainFrame(
406     const content::LoadCommittedDetails& details,
407     const content::FrameNavigateParams& params) {
408   // Content settings are based on the main frame, so if it switches then
409   // close up shop.
410   content_setting_bubble_model_->OnDoneClicked();
411   GetWidget()->Close();
412 }
413
414 void ContentSettingBubbleContents::OnNativeThemeChanged(
415     const ui::NativeTheme* theme) {
416   views::BubbleDelegateView::OnNativeThemeChanged(theme);
417   UpdateMenuButtonSizes(theme);
418 }
419
420 void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
421                                                  const ui::Event& event) {
422   RadioGroup::const_iterator i(
423       std::find(radio_group_.begin(), radio_group_.end(), sender));
424   if (i != radio_group_.end()) {
425     content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
426     return;
427   }
428   DCHECK_EQ(sender, close_button_);
429   content_setting_bubble_model_->OnDoneClicked();
430   GetWidget()->Close();
431 }
432
433 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
434                                                int event_flags) {
435   if (source == learn_more_link_) {
436     content_setting_bubble_model_->OnLearnMoreLinkClicked();
437     GetWidget()->Close();
438     return;
439   }
440   if (source == custom_link_) {
441     content_setting_bubble_model_->OnCustomLinkClicked();
442     GetWidget()->Close();
443     return;
444   }
445   if (source == manage_link_) {
446     GetWidget()->Close();
447     content_setting_bubble_model_->OnManageLinkClicked();
448     // CAREFUL: Showing the settings window activates it, which deactivates the
449     // info bubble, which causes it to close, which deletes us.
450     return;
451   }
452
453   PopupLinks::const_iterator i(popup_links_.find(source));
454   DCHECK(i != popup_links_.end());
455   content_setting_bubble_model_->OnPopupClicked(i->second);
456 }
457
458 void ContentSettingBubbleContents::OnMenuButtonClicked(
459     views::View* source,
460     const gfx::Point& point) {
461     MediaMenuPartsMap::iterator j(media_menus_.find(
462         static_cast<views::MenuButton*>(source)));
463     DCHECK(j != media_menus_.end());
464     menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get(),
465                                              views::MenuRunner::HAS_MNEMONICS));
466
467     gfx::Point screen_location;
468     views::View::ConvertPointToScreen(j->first, &screen_location);
469     ignore_result(
470         menu_runner_->RunMenuAt(source->GetWidget(),
471                                 j->first,
472                                 gfx::Rect(screen_location, j->first->size()),
473                                 views::MENU_ANCHOR_TOPLEFT,
474                                 ui::MENU_SOURCE_NONE));
475 }
476
477 void ContentSettingBubbleContents::UpdateMenuButtonSizes(
478     const ui::NativeTheme* theme) {
479   const views::MenuConfig config = views::MenuConfig(theme);
480   const int margins = config.item_left_margin + config.check_width +
481                       config.label_to_arrow_padding + config.arrow_width +
482                       config.arrow_to_edge_padding;
483
484   // The preferred media menu size sort of copies the logic in
485   // MenuItemView::CalculateDimensions(). When this was using TextButton, it
486   // completely coincidentally matched the logic in MenuItemView. We now need
487   // to redo this manually.
488   int menu_width = 0;
489   for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
490        i != media_menus_.end(); ++i) {
491     for (int j = 0; j < i->second->menu_model->GetItemCount(); ++j) {
492       int string_width = gfx::GetStringWidth(
493           i->second->menu_model->GetLabelAt(j),
494           config.font_list);
495
496       menu_width = std::max(menu_width, string_width);
497     }
498   }
499
500   // Make sure the width is at least kMinMediaMenuButtonWidth. The
501   // maximum width will be clamped by kMaxContentsWidth of the view.
502   menu_width = std::max(kMinMediaMenuButtonWidth, menu_width + margins);
503
504   for (MediaMenuPartsMap::const_iterator i = media_menus_.begin();
505        i != media_menus_.end(); ++i) {
506     i->first->SetMinSize(gfx::Size(menu_width, 0));
507     i->first->SetMaxSize(gfx::Size(menu_width, 0));
508   }
509 }