blob: 09e63c094f7d18a9eb77431bca1b6c7e9d1d42c0 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/content_setting_bubble_contents.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/content_setting_domain_list_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/native_cursor.h"
namespace {
enum class LayoutRowType {
DEFAULT,
INDENTED,
FULL_WIDTH,
};
// Represents a row containing a single view in the layout. The type determines
// the view's horizontal margins.
struct LayoutRow {
std::unique_ptr<views::View> view;
LayoutRowType type;
};
// A combobox model that builds the contents of the media capture devices menu
// in the content setting bubble.
class MediaComboboxModel : public ui::ComboboxModel {
public:
explicit MediaComboboxModel(blink::mojom::MediaStreamType type);
~MediaComboboxModel() override;
blink::mojom::MediaStreamType type() const { return type_; }
const blink::MediaStreamDevices& GetDevices() const;
int GetDeviceIndex(const blink::MediaStreamDevice& device) const;
// ui::ComboboxModel:
int GetItemCount() const override;
std::u16string GetItemAt(int index) const override;
private:
blink::mojom::MediaStreamType type_;
DISALLOW_COPY_AND_ASSIGN(MediaComboboxModel);
};
// A view representing one or more rows, each containing a label and combobox
// pair, that allow the user to select a device for each media type (microphone
// and/or camera).
class MediaMenuBlock : public views::View {
public:
METADATA_HEADER(MediaMenuBlock);
MediaMenuBlock(base::RepeatingCallback<void(views::Combobox*)> callback,
ContentSettingBubbleModel::MediaMenuMap media) {
const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
views::GridLayout* layout =
SetLayoutManager(std::make_unique<views::GridLayout>());
constexpr int kColumnSetId = 0;
views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId);
column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
views::GridLayout::kFixedSize,
views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
column_set->AddPaddingColumn(
views::GridLayout::kFixedSize,
provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL));
column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0,
views::GridLayout::ColumnSize::kFixed, 0, 0);
bool first_row = true;
for (auto i = media.cbegin(); i != media.cend(); ++i) {
if (!first_row) {
layout->AddPaddingRow(views::GridLayout::kFixedSize,
provider->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL));
}
first_row = false;
layout->StartRow(views::GridLayout::kFixedSize, kColumnSetId);
blink::mojom::MediaStreamType stream_type = i->first;
const ContentSettingBubbleModel::MediaMenu& menu = i->second;
auto label = std::make_unique<views::Label>(menu.label);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
layout->AddView(std::move(label));
auto combobox_model = std::make_unique<MediaComboboxModel>(stream_type);
// Disable the device selection when the website is managing the devices
// itself or if there are no devices present.
const bool combobox_enabled =
!menu.disabled && !combobox_model->GetDevices().empty();
const int combobox_selected_index =
combobox_model->GetDevices().empty()
? 0
: combobox_model->GetDeviceIndex(menu.selected_device);
// The combobox takes ownership of the model.
auto combobox =
std::make_unique<views::Combobox>(std::move(combobox_model));
combobox->SetEnabled(combobox_enabled);
combobox->SetCallback(base::BindRepeating(callback, combobox.get()));
combobox->SetSelectedIndex(combobox_selected_index);
layout->AddView(std::move(combobox));
}
}
MediaMenuBlock(const MediaMenuBlock&) = delete;
MediaMenuBlock& operator=(const MediaMenuBlock&) = delete;
};
BEGIN_METADATA(MediaMenuBlock, views::View)
END_METADATA
} // namespace
// MediaComboboxModel ----------------------------------------------------------
MediaComboboxModel::MediaComboboxModel(blink::mojom::MediaStreamType type)
: type_(type) {
DCHECK(type_ == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE ||
type_ == blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE);
}
MediaComboboxModel::~MediaComboboxModel() {}
const blink::MediaStreamDevices& MediaComboboxModel::GetDevices() const {
MediaCaptureDevicesDispatcher* dispatcher =
MediaCaptureDevicesDispatcher::GetInstance();
return type_ == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
? dispatcher->GetAudioCaptureDevices()
: dispatcher->GetVideoCaptureDevices();
}
int MediaComboboxModel::GetDeviceIndex(
const blink::MediaStreamDevice& device) const {
const auto& devices = GetDevices();
for (size_t i = 0; i < devices.size(); ++i) {
if (device.id == devices[i].id)
return i;
}
NOTREACHED();
return 0;
}
int MediaComboboxModel::GetItemCount() const {
return std::max(1, static_cast<int>(GetDevices().size()));
}
std::u16string MediaComboboxModel::GetItemAt(int index) const {
return GetDevices().empty()
? l10n_util::GetStringUTF16(IDS_MEDIA_MENU_NO_DEVICE_TITLE)
: base::UTF8ToUTF16(GetDevices()[index].name);
}
// ContentSettingBubbleContents::ListItemContainer -----------------------------
class ContentSettingBubbleContents::ListItemContainer : public views::View {
public:
METADATA_HEADER(ListItemContainer);
explicit ListItemContainer(ContentSettingBubbleContents* parent);
ListItemContainer(const ListItemContainer&) = delete;
ListItemContainer& operator=(const ListItemContainer&) = delete;
// Creates and adds child views representing |item|.
void AddItem(const ContentSettingBubbleModel::ListItem& item);
// Calling this will delete related children.
void RemoveRowAtIndex(int index);
private:
using Row = std::pair<views::ImageView*, views::View*>;
using NewRow = std::pair<std::unique_ptr<views::ImageView>,
std::unique_ptr<views::View>>;
void ResetLayout();
void AddRowToLayout(const Row& row);
Row AddNewRowToLayout(NewRow row);
void UpdateScrollHeight(const Row& row);
ContentSettingBubbleContents* parent_;
// Our controls representing list items, so we can add or remove
// these dynamically. Each pair represents one list item.
std::vector<Row> list_item_views_;
};
ContentSettingBubbleContents::ListItemContainer::ListItemContainer(
ContentSettingBubbleContents* parent)
: parent_(parent) {
ResetLayout();
}
void ContentSettingBubbleContents::ListItemContainer::AddItem(
const ContentSettingBubbleModel::ListItem& item) {
// Padding for list items and icons.
static constexpr gfx::Insets kTitleDescriptionListItemInset(3, 0, 13, 0);
auto item_icon = std::make_unique<views::ImageView>();
if (item.image) {
item_icon->SetBorder(
views::CreateEmptyBorder(kTitleDescriptionListItemInset));
const SkColor icon_color =
views::style::GetColor(*item_icon, CONTEXT_DIALOG_BODY_TEXT_SMALL,
views::style::STYLE_PRIMARY);
item_icon->SetImage(CreateVectorIconWithBadge(
*item.image, GetLayoutConstant(LOCATION_BAR_ICON_SIZE), icon_color,
item.has_blocked_badge ? vector_icons::kBlockedBadgeIcon
: gfx::kNoneIcon));
}
std::unique_ptr<views::View> item_contents;
if (item.has_link) {
auto link = std::make_unique<views::Link>(item.title);
link->SetElideBehavior(gfx::ELIDE_MIDDLE);
link->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
link->SetCallback(base::BindRepeating(
[](const std::vector<Row>* items, const views::Link* link,
ContentSettingBubbleContents* parent, const ui::Event& event) {
const auto it = base::ranges::find(*items, link, &Row::second);
DCHECK(it != items->cend());
parent->LinkClicked(std::distance(items->cbegin(), it), event);
},
base::Unretained(&list_item_views_), base::Unretained(link.get()),
base::Unretained(parent_)));
item_contents = std::move(link);
} else {
item_contents = std::make_unique<views::View>();
item_contents->SetBorder(
views::CreateEmptyBorder(kTitleDescriptionListItemInset));
item_contents->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
const auto add_label = [&item_contents](const std::u16string& string,
int style) {
if (!string.empty()) {
auto label = std::make_unique<views::Label>(
string, views::style::CONTEXT_LABEL, style,
gfx::DirectionalityMode::DIRECTIONALITY_FROM_UI);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label->SetAllowCharacterBreak(true);
item_contents->AddChildView(std::move(label));
}
};
add_label(item.title, views::style::STYLE_PRIMARY);
add_label(item.description, views::style::STYLE_DISABLED);
}
list_item_views_.push_back(AddNewRowToLayout(
NewRow(std::move(item_icon), std::move(item_contents))));
}
void ContentSettingBubbleContents::ListItemContainer::RemoveRowAtIndex(
int index) {
auto& children = list_item_views_[index];
delete children.first;
delete children.second;
list_item_views_.erase(list_item_views_.begin() + index);
// As GridLayout can't remove rows, we have to rebuild it entirely.
ResetLayout();
for (auto& row : list_item_views_)
AddRowToLayout(row);
}
void ContentSettingBubbleContents::ListItemContainer::ResetLayout() {
views::GridLayout* layout =
SetLayoutManager(std::make_unique<views::GridLayout>());
views::ColumnSet* item_list_column_set = layout->AddColumnSet(0);
item_list_column_set->AddColumn(
views::GridLayout::LEADING, views::GridLayout::FILL,
views::GridLayout::kFixedSize,
views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
const int related_control_horizontal_spacing =
ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
item_list_column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
related_control_horizontal_spacing);
item_list_column_set->AddColumn(
views::GridLayout::LEADING, views::GridLayout::FILL, 1.0,
views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
auto* scroll_view = views::ScrollView::GetScrollViewForContents(this);
// When this function is called from the constructor, the view has not yet
// been placed into a ScrollView.
if (scroll_view)
scroll_view->ClipHeightTo(-1, -1);
}
void ContentSettingBubbleContents::ListItemContainer::AddRowToLayout(
const Row& row) {
views::GridLayout* layout =
static_cast<views::GridLayout*>(GetLayoutManager());
DCHECK(layout);
layout->StartRow(views::GridLayout::kFixedSize, 0);
layout->AddExistingView(row.first);
layout->AddExistingView(row.second);
UpdateScrollHeight(row);
}
ContentSettingBubbleContents::ListItemContainer::Row
ContentSettingBubbleContents::ListItemContainer::AddNewRowToLayout(NewRow row) {
views::GridLayout* layout =
static_cast<views::GridLayout*>(GetLayoutManager());
DCHECK(layout);
Row row_result;
layout->StartRow(views::GridLayout::kFixedSize, 0);
row_result.first = layout->AddView(std::move(row.first));
row_result.second = layout->AddView(std::move(row.second));
UpdateScrollHeight(row_result);
return row_result;
}
void ContentSettingBubbleContents::ListItemContainer::UpdateScrollHeight(
const Row& row) {
auto* scroll_view = views::ScrollView::GetScrollViewForContents(this);
DCHECK(scroll_view);
if (!scroll_view->is_bounded()) {
// Display a maximum of 4 visible items in a list before scrolling.
static constexpr int kMaxVisibleListItems = 4;
scroll_view->ClipHeightTo(
0, std::max(row.first->GetPreferredSize().height(),
row.second->GetPreferredSize().height()) *
kMaxVisibleListItems);
}
}
BEGIN_METADATA(ContentSettingBubbleContents, ListItemContainer, views::View)
END_METADATA
// ContentSettingBubbleContents -----------------------------------------------
ContentSettingBubbleContents::ContentSettingBubbleContents(
std::unique_ptr<ContentSettingBubbleModel> content_setting_bubble_model,
content::WebContents* web_contents,
views::View* anchor_view,
views::BubbleBorder::Arrow arrow)
: content::WebContentsObserver(web_contents),
BubbleDialogDelegateView(anchor_view, arrow),
content_setting_bubble_model_(std::move(content_setting_bubble_model)) {
chrome::RecordDialogCreation(
chrome::DialogIdentifier::CONTENT_SETTING_CONTENTS);
// Although other code in this class treats content_setting_bubble_model_ as
// though it's optional, in fact it can only become null if
// WebContentsDestroyed() is called, which can't happen until the constructor
// has run - so it is never null here.
DCHECK(content_setting_bubble_model_);
const std::u16string& done_text =
content_setting_bubble_model_->bubble_content().done_button_text;
const std::u16string& cancel_text =
content_setting_bubble_model_->bubble_content().cancel_button_text;
SetButtons(cancel_text.empty()
? ui::DIALOG_BUTTON_OK
: (ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL));
SetButtonLabel(ui::DIALOG_BUTTON_OK, done_text.empty()
? l10n_util::GetStringUTF16(IDS_DONE)
: done_text);
SetExtraView(CreateHelpAndManageView());
SetAcceptCallback(
base::BindOnce(&ContentSettingBubbleModel::OnDoneButtonClicked,
base::Unretained(content_setting_bubble_model_.get())));
if (!cancel_text.empty()) {
SetButtonLabel(ui::DIALOG_BUTTON_CANCEL, cancel_text);
SetCancelCallback(
base::BindOnce(&ContentSettingBubbleModel::OnCancelButtonClicked,
base::Unretained(content_setting_bubble_model_.get())));
}
set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
}
ContentSettingBubbleContents::~ContentSettingBubbleContents() {
// Must remove the children here so the comboboxes get destroyed before
// their associated models.
RemoveAllChildViews(true);
}
void ContentSettingBubbleContents::WindowClosing() {
if (content_setting_bubble_model_)
content_setting_bubble_model_->CommitChanges();
}
void ContentSettingBubbleContents::OnListItemAdded(
const ContentSettingBubbleModel::ListItem& item) {
DCHECK(list_item_container_);
list_item_container_->AddItem(item);
SizeToContents();
}
void ContentSettingBubbleContents::OnListItemRemovedAt(int index) {
DCHECK(list_item_container_);
list_item_container_->RemoveRowAtIndex(index);
SizeToContents();
}
int ContentSettingBubbleContents::GetSelectedRadioOption() {
for (RadioGroup::const_iterator i(radio_group_.begin());
i != radio_group_.end(); ++i) {
if ((*i)->GetChecked())
return i - radio_group_.begin();
}
NOTREACHED();
return 0;
}
void ContentSettingBubbleContents::OnThemeChanged() {
views::BubbleDialogDelegateView::OnThemeChanged();
if (learn_more_button_)
StyleLearnMoreButton();
}
std::u16string ContentSettingBubbleContents::GetWindowTitle() const {
if (!content_setting_bubble_model_)
return std::u16string();
return content_setting_bubble_model_->bubble_content().title;
}
bool ContentSettingBubbleContents::ShouldShowCloseButton() const {
return true;
}
void ContentSettingBubbleContents::Init() {
DCHECK(content_setting_bubble_model_);
const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
std::vector<LayoutRow> rows;
const ContentSettingBubbleModel::BubbleContent& bubble_content =
content_setting_bubble_model_->bubble_content();
if (!bubble_content.message.empty()) {
auto message_label = std::make_unique<views::Label>(
bubble_content.message, views::style::CONTEXT_LABEL,
views::style::STYLE_SECONDARY);
message_label->SetMultiLine(true);
message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
rows.push_back({std::move(message_label), LayoutRowType::DEFAULT});
}
// Layout for the item list (blocked plugins and popups).
if (!bubble_content.list_items.empty()) {
auto list_item_container = std::make_unique<ListItemContainer>(this);
list_item_container->SetBorder(
views::CreateEmptyBorder(0, margins().left(), 0, margins().right()));
auto scroll_view = std::make_unique<views::ScrollView>();
list_item_container_ =
scroll_view->SetContents(std::move(list_item_container));
rows.push_back({std::move(scroll_view), LayoutRowType::FULL_WIDTH});
for (const ContentSettingBubbleModel::ListItem& list_item :
bubble_content.list_items) {
list_item_container_->AddItem(list_item);
}
}
const ContentSettingBubbleModel::RadioGroup& radio_group =
bubble_content.radio_group;
if (!radio_group.radio_items.empty()) {
for (auto i(radio_group.radio_items.begin());
i != radio_group.radio_items.end(); ++i) {
auto radio = std::make_unique<views::RadioButton>(*i, 0);
radio->SetEnabled(radio_group.user_managed);
radio->SetMultiLine(true);
radio_group_.push_back(radio.get());
rows.push_back({std::move(radio), LayoutRowType::INDENTED});
}
DCHECK(!radio_group_.empty());
// Now that the buttons have been added to the view hierarchy, it's safe
// to call SetChecked() on them.
radio_group_[radio_group.default_item]->SetChecked(true);
}
// Layout code for the media device menus.
if (content_setting_bubble_model_->AsMediaStreamBubbleModel()) {
rows.push_back(
{std::make_unique<MediaMenuBlock>(
base::BindRepeating(&ContentSettingBubbleContents::OnPerformAction,
base::Unretained(this)),
bubble_content.media_menus),
LayoutRowType::INDENTED});
}
for (auto i(bubble_content.domain_lists.begin());
i != bubble_content.domain_lists.end(); ++i) {
auto list_view =
std::make_unique<ContentSettingDomainListView>(i->title, i->hosts);
rows.push_back({std::move(list_view), LayoutRowType::DEFAULT});
}
if (!bubble_content.custom_link.empty()) {
auto custom_link =
std::make_unique<views::Link>(bubble_content.custom_link);
custom_link->SetEnabled(bubble_content.custom_link_enabled);
custom_link->SetMultiLine(true);
custom_link->SetCallback(
base::BindRepeating(&ContentSettingBubbleContents::CustomLinkClicked,
base::Unretained(this)));
custom_link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
rows.push_back({std::move(custom_link), LayoutRowType::DEFAULT});
}
if (bubble_content.manage_text_style ==
ContentSettingBubbleModel::ManageTextStyle::kCheckbox) {
auto manage_checkbox = std::make_unique<views::Checkbox>(
bubble_content.manage_text,
base::BindRepeating(
[](ContentSettingBubbleContents* bubble) {
bubble->content_setting_bubble_model_->OnManageCheckboxChecked(
bubble->manage_checkbox_->GetChecked());
// Toggling the check state may change the dialog button text.
bubble->DialogModelChanged();
},
base::Unretained(this)));
manage_checkbox_ = manage_checkbox.get();
rows.push_back({std::move(manage_checkbox), LayoutRowType::DEFAULT});
}
// We have to apply the left and right margins manually, because rows using
// LayoutRowType::FULL_WIDTH need to not have them applied to look correct.
const int left_margin = margins().left();
const int right_margin = margins().right();
set_margins(gfx::Insets(margins().top(), 0, margins().bottom(), 0));
for (LayoutRow& row : rows) {
if (row.type != LayoutRowType::FULL_WIDTH) {
const int row_left_margin =
left_margin + (row.type == LayoutRowType::INDENTED
? provider->GetDistanceMetric(
DISTANCE_SUBSECTION_HORIZONTAL_INDENT)
: 0);
row.view->SetBorder(
views::CreateEmptyBorder(0, row_left_margin, 0, right_margin));
}
AddChildView(std::move(row.view));
}
content_setting_bubble_model_->set_owner(this);
}
void ContentSettingBubbleContents::StyleLearnMoreButton() {
DCHECK(learn_more_button_);
SkColor text_color = GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_LabelEnabledColor);
views::SetImageFromVectorIcon(learn_more_button_,
vector_icons::kHelpOutlineIcon, text_color);
}
std::unique_ptr<views::View>
ContentSettingBubbleContents::CreateHelpAndManageView() {
DCHECK(content_setting_bubble_model_);
const auto& bubble_content = content_setting_bubble_model_->bubble_content();
const auto* layout = ChromeLayoutProvider::Get();
std::vector<std::unique_ptr<views::View>> extra_views;
// Optionally add a help icon if the view wants to link to a help page.
if (bubble_content.show_learn_more) {
auto learn_more_button = views::CreateVectorImageButton(base::BindRepeating(
[](ContentSettingBubbleContents* bubble) {
bubble->GetWidget()->Close();
bubble->content_setting_bubble_model_->OnLearnMoreClicked();
},
base::Unretained(this)));
learn_more_button->SetTooltipText(
l10n_util::GetStringUTF16(IDS_LEARN_MORE));
learn_more_button_ = learn_more_button.get();
extra_views.push_back(std::move(learn_more_button));
}
// Optionally add a "Manage" button if the view wants to use a button to
// invoke a separate management UI related to the dialog content.
if (bubble_content.manage_text_style ==
ContentSettingBubbleModel::ManageTextStyle::kButton) {
std::u16string title = bubble_content.manage_text;
if (title.empty())
title = l10n_util::GetStringUTF16(IDS_MANAGE);
auto manage_button = std::make_unique<views::MdTextButton>(
base::BindRepeating(
[](ContentSettingBubbleContents* bubble) {
bubble->GetWidget()->Close();
bubble->content_setting_bubble_model_->OnManageButtonClicked();
},
base::Unretained(this)),
title);
manage_button->SetMinSize(gfx::Size(
layout->GetDistanceMetric(views::DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH),
0));
manage_button_ = manage_button.get();
extra_views.push_back(std::move(manage_button));
}
if (extra_views.empty())
return nullptr;
if (extra_views.size() == 1)
return std::move(extra_views.front());
auto container = std::make_unique<views::View>();
container->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
layout->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
for (auto& extra_view : extra_views)
container->AddChildView(std::move(extra_view));
return container;
}
void ContentSettingBubbleContents::LinkClicked(int row,
const ui::Event& event) {
DCHECK(content_setting_bubble_model_);
DCHECK_NE(row, -1);
content_setting_bubble_model_->OnListItemClicked(row, event);
}
void ContentSettingBubbleContents::CustomLinkClicked() {
DCHECK(content_setting_bubble_model_);
content_setting_bubble_model_->OnCustomLinkClicked();
GetWidget()->Close();
}
void ContentSettingBubbleContents::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted())
return;
// Content settings are based on the main frame, so if it switches then
// close up shop.
GetWidget()->Close();
}
void ContentSettingBubbleContents::OnVisibilityChanged(
content::Visibility visibility) {
if (visibility == content::Visibility::HIDDEN)
GetWidget()->Close();
}
void ContentSettingBubbleContents::WebContentsDestroyed() {
// Destroy the bubble model to ensure that the underlying WebContents outlives
// it.
content_setting_bubble_model_->CommitChanges();
content_setting_bubble_model_.reset();
// Closing the widget should synchronously hide it (and post a task to delete
// it). Subsequent event listener methods should not be invoked on hidden
// widgets.
GetWidget()->Close();
}
void ContentSettingBubbleContents::OnPerformAction(views::Combobox* combobox) {
DCHECK(content_setting_bubble_model_);
MediaComboboxModel* model =
static_cast<MediaComboboxModel*>(combobox->GetModel());
content_setting_bubble_model_->OnMediaMenuClicked(
model->type(), model->GetDevices()[combobox->GetSelectedIndex()].id);
}
BEGIN_METADATA(ContentSettingBubbleContents, views::BubbleDialogDelegateView)
END_METADATA