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