Home | History | Annotate | Download | only in views
      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 
     45 namespace {
     46 
     47 // If we don't clamp the maximum width, then very long URLs and titles can make
     48 // the bubble arbitrarily wide.
     49 const 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.
     53 const int kMinMultiLineContentsWidth = 250;
     54 
     55 // The minimum width of the media menu buttons.
     56 const int kMinMediaMenuButtonWidth = 100;
     57 
     58 }  // namespace
     59 
     60 using content::PluginService;
     61 using content::WebContents;
     62 
     63 
     64 // ContentSettingBubbleContents::Favicon --------------------------------------
     65 
     66 class 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 
     83 ContentSettingBubbleContents::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 
     92 ContentSettingBubbleContents::Favicon::~Favicon() {
     93 }
     94 
     95 bool ContentSettingBubbleContents::Favicon::OnMousePressed(
     96     const ui::MouseEvent& event) {
     97   return event.IsLeftMouseButton() || event.IsMiddleMouseButton();
     98 }
     99 
    100 void 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 
    108 gfx::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 
    121 struct 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 
    132 ContentSettingBubbleContents::MediaMenuParts::MediaMenuParts(
    133     content::MediaStreamType type)
    134     : type(type) {}
    135 
    136 ContentSettingBubbleContents::MediaMenuParts::~MediaMenuParts() {}
    137 
    138 // ContentSettingBubbleContents -----------------------------------------------
    139 
    140 ContentSettingBubbleContents::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       learn_more_link_(NULL),
    151       close_button_(NULL) {
    152   // Compensate for built-in vertical padding in the anchor view's image.
    153   set_anchor_view_insets(gfx::Insets(5, 0, 5, 0));
    154 }
    155 
    156 ContentSettingBubbleContents::~ContentSettingBubbleContents() {
    157   STLDeleteValues(&media_menus_);
    158 }
    159 
    160 gfx::Size ContentSettingBubbleContents::GetPreferredSize() const {
    161   gfx::Size preferred_size(views::View::GetPreferredSize());
    162   int preferred_width =
    163       (!content_setting_bubble_model_->bubble_content().domain_lists.empty() &&
    164        (kMinMultiLineContentsWidth > preferred_size.width())) ?
    165       kMinMultiLineContentsWidth : preferred_size.width();
    166   preferred_size.set_width(std::min(preferred_width, kMaxContentsWidth));
    167   return preferred_size;
    168 }
    169 
    170 void ContentSettingBubbleContents::UpdateMenuLabel(
    171     content::MediaStreamType type,
    172     const std::string& label) {
    173   for (MediaMenuPartsMap::const_iterator it = media_menus_.begin();
    174        it != media_menus_.end(); ++it) {
    175     if (it->second->type == type) {
    176       it->first->SetText(base::UTF8ToUTF16(label));
    177       return;
    178     }
    179   }
    180   NOTREACHED();
    181 }
    182 
    183 void ContentSettingBubbleContents::Init() {
    184   using views::GridLayout;
    185 
    186   GridLayout* layout = new views::GridLayout(this);
    187   SetLayoutManager(layout);
    188 
    189   const int kSingleColumnSetId = 0;
    190   views::ColumnSet* column_set = layout->AddColumnSet(kSingleColumnSetId);
    191   column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
    192                         GridLayout::USE_PREF, 0, 0);
    193   column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
    194   column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
    195                         GridLayout::USE_PREF, 0, 0);
    196 
    197   const ContentSettingBubbleModel::BubbleContent& bubble_content =
    198       content_setting_bubble_model_->bubble_content();
    199   bool bubble_content_empty = true;
    200 
    201   if (!bubble_content.title.empty()) {
    202     views::Label* title_label = new views::Label(base::UTF8ToUTF16(
    203         bubble_content.title));
    204     title_label->SetMultiLine(true);
    205     title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    206     layout->StartRow(0, kSingleColumnSetId);
    207     layout->AddView(title_label);
    208     bubble_content_empty = false;
    209   }
    210 
    211   if (!bubble_content.learn_more_link.empty()) {
    212     learn_more_link_ =
    213         new views::Link(base::UTF8ToUTF16(bubble_content.learn_more_link));
    214     learn_more_link_->set_listener(this);
    215     learn_more_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    216     layout->AddView(learn_more_link_);
    217     bubble_content_empty = false;
    218   }
    219 
    220   if (content_setting_bubble_model_->content_type() ==
    221       CONTENT_SETTINGS_TYPE_POPUPS) {
    222     const int kPopupColumnSetId = 2;
    223     views::ColumnSet* popup_column_set =
    224         layout->AddColumnSet(kPopupColumnSetId);
    225     popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
    226                                 GridLayout::USE_PREF, 0, 0);
    227     popup_column_set->AddPaddingColumn(
    228         0, views::kRelatedControlHorizontalSpacing);
    229     popup_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
    230                                 GridLayout::USE_PREF, 0, 0);
    231 
    232     for (std::vector<ContentSettingBubbleModel::PopupItem>::const_iterator
    233          i(bubble_content.popup_items.begin());
    234          i != bubble_content.popup_items.end(); ++i) {
    235       if (!bubble_content_empty)
    236         layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    237       layout->StartRow(0, kPopupColumnSetId);
    238 
    239       views::Link* link = new views::Link(base::UTF8ToUTF16(i->title));
    240       link->set_listener(this);
    241       link->SetElideBehavior(gfx::ELIDE_MIDDLE);
    242       popup_links_[link] = i - bubble_content.popup_items.begin();
    243       layout->AddView(new Favicon(i->image, this, link));
    244       layout->AddView(link);
    245       bubble_content_empty = false;
    246     }
    247   }
    248 
    249   const int indented_kSingleColumnSetId = 3;
    250   // Insert a column set with greater indent.
    251   views::ColumnSet* indented_single_column_set =
    252       layout->AddColumnSet(indented_kSingleColumnSetId);
    253   indented_single_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
    254   indented_single_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL,
    255                                         1, GridLayout::USE_PREF, 0, 0);
    256 
    257   const ContentSettingBubbleModel::RadioGroup& radio_group =
    258       bubble_content.radio_group;
    259   if (!radio_group.radio_items.empty()) {
    260     if (!bubble_content_empty)
    261       layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
    262     for (ContentSettingBubbleModel::RadioItems::const_iterator i(
    263          radio_group.radio_items.begin());
    264          i != radio_group.radio_items.end(); ++i) {
    265       views::RadioButton* radio =
    266           new views::RadioButton(base::UTF8ToUTF16(*i), 0);
    267       radio->SetEnabled(bubble_content.radio_group_enabled);
    268       radio->set_listener(this);
    269       radio_group_.push_back(radio);
    270       layout->StartRow(0, indented_kSingleColumnSetId);
    271       layout->AddView(radio);
    272       bubble_content_empty = false;
    273     }
    274     DCHECK(!radio_group_.empty());
    275     // Now that the buttons have been added to the view hierarchy, it's safe
    276     // to call SetChecked() on them.
    277     radio_group_[radio_group.default_item]->SetChecked(true);
    278   }
    279 
    280   // Layout code for the media device menus.
    281   if (content_setting_bubble_model_->content_type() ==
    282       CONTENT_SETTINGS_TYPE_MEDIASTREAM) {
    283     const int kMediaMenuColumnSetId = 2;
    284     views::ColumnSet* menu_column_set =
    285         layout->AddColumnSet(kMediaMenuColumnSetId);
    286     menu_column_set->AddPaddingColumn(0, views::kCheckboxIndent);
    287     menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 0,
    288                                GridLayout::USE_PREF, 0, 0);
    289     menu_column_set->AddPaddingColumn(
    290         0, views::kRelatedControlHorizontalSpacing);
    291     menu_column_set->AddColumn(GridLayout::LEADING, GridLayout::FILL, 1,
    292                                GridLayout::USE_PREF, 0, 0);
    293 
    294     int menu_width = 0;
    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       // 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_size(gfx::Size(menu_width, 0));
    353       i->first->set_max_size(gfx::Size(menu_width, 0));
    354     }
    355   }
    356 
    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));
    372     }
    373     bubble_content_empty = false;
    374   }
    375 
    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;
    386   }
    387 
    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);
    397     }
    398 
    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);
    405 
    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_);
    411 
    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_);
    416 }
    417 
    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();
    425 }
    426 
    427 void ContentSettingBubbleContents::ButtonPressed(views::Button* sender,
    428                                                  const ui::Event& event) {
    429   RadioGroup::const_iterator i(
    430       std::find(radio_group_.begin(), radio_group_.end(), sender));
    431   if (i != radio_group_.end()) {
    432     content_setting_bubble_model_->OnRadioClicked(i - radio_group_.begin());
    433     return;
    434   }
    435   DCHECK_EQ(sender, close_button_);
    436   content_setting_bubble_model_->OnDoneClicked();
    437   GetWidget()->Close();
    438 }
    439 
    440 void ContentSettingBubbleContents::LinkClicked(views::Link* source,
    441                                                int event_flags) {
    442   if (source == learn_more_link_) {
    443     content_setting_bubble_model_->OnLearnMoreLinkClicked();
    444     GetWidget()->Close();
    445     return;
    446   }
    447   if (source == custom_link_) {
    448     content_setting_bubble_model_->OnCustomLinkClicked();
    449     GetWidget()->Close();
    450     return;
    451   }
    452   if (source == manage_link_) {
    453     GetWidget()->Close();
    454     content_setting_bubble_model_->OnManageLinkClicked();
    455     // CAREFUL: Showing the settings window activates it, which deactivates the
    456     // info bubble, which causes it to close, which deletes us.
    457     return;
    458   }
    459 
    460   PopupLinks::const_iterator i(popup_links_.find(source));
    461   DCHECK(i != popup_links_.end());
    462   content_setting_bubble_model_->OnPopupClicked(i->second);
    463 }
    464 
    465 void ContentSettingBubbleContents::OnMenuButtonClicked(
    466     views::View* source,
    467     const gfx::Point& point) {
    468     MediaMenuPartsMap::iterator j(media_menus_.find(
    469         static_cast<views::MenuButton*>(source)));
    470     DCHECK(j != media_menus_.end());
    471     menu_runner_.reset(new views::MenuRunner(j->second->menu_model.get()));
    472 
    473     gfx::Point screen_location;
    474     views::View::ConvertPointToScreen(j->first, &screen_location);
    475     ignore_result(
    476         menu_runner_->RunMenuAt(source->GetWidget(),
    477                                 j->first,
    478                                 gfx::Rect(screen_location, j->first->size()),
    479                                 views::MENU_ANCHOR_TOPLEFT,
    480                                 ui::MENU_SOURCE_NONE,
    481                                 views::MenuRunner::HAS_MNEMONICS));
    482 }
    483 
    484 int ContentSettingBubbleContents::GetPreferredMediaMenuWidth(
    485     views::MenuButton* button,
    486     ui::SimpleMenuModel* menu_model) {
    487   base::string16 title = button->GetText();
    488 
    489   int width = button->GetPreferredSize().width();
    490   for (int i = 0; i < menu_model->GetItemCount(); ++i) {
    491     button->SetText(menu_model->GetLabelAt(i));
    492     width = std::max(width, button->GetPreferredSize().width());
    493   }
    494 
    495   // Recover the title for the menu button.
    496   button->SetText(title);
    497   return width;
    498 }
    499