Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / ui / views / website_settings / permissions_bubble_view.cc
blob1d287e3ce581e1310f22585f908d6dc7ebceb2a9
1 // Copyright 2014 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/website_settings/permissions_bubble_view.h"
7 #include "base/prefs/pref_service.h"
8 #include "base/strings/string16.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
12 #include "chrome/browser/ui/views/frame/browser_view.h"
13 #include "chrome/browser/ui/views/frame/top_container_view.h"
14 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
15 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
16 #include "chrome/browser/ui/views/website_settings/permission_selector_view.h"
17 #include "chrome/browser/ui/views/website_settings/permission_selector_view_observer.h"
18 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "components/url_formatter/elide_url.h"
22 #include "ui/accessibility/ax_view_state.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/models/combobox_model.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/paint_vector_icon.h"
27 #include "ui/gfx/text_constants.h"
28 #include "ui/gfx/vector_icons_public.h"
29 #include "ui/native_theme/common_theme.h"
30 #include "ui/native_theme/native_theme.h"
31 #include "ui/views/bubble/bubble_delegate.h"
32 #include "ui/views/bubble/bubble_frame_view.h"
33 #include "ui/views/controls/button/checkbox.h"
34 #include "ui/views/controls/button/label_button.h"
35 #include "ui/views/controls/button/label_button_border.h"
36 #include "ui/views/controls/button/menu_button.h"
37 #include "ui/views/controls/button/menu_button_listener.h"
38 #include "ui/views/controls/combobox/combobox.h"
39 #include "ui/views/controls/combobox/combobox_listener.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/controls/menu/menu_runner.h"
42 #include "ui/views/layout/box_layout.h"
43 #include "ui/views/layout/grid_layout.h"
45 namespace {
47 // Spacing constant for outer margin. This is added to the
48 // bubble margin itself to equalize the margins at 13px.
49 const int kBubbleOuterMargin = 5;
51 // Spacing between major items should be 9px.
52 const int kItemMajorSpacing = 9;
54 // Button border size, draws inside the spacing distance.
55 const int kButtonBorderSize = 2;
57 // (Square) pixel size of icon.
58 const int kIconSize = 18;
60 // Number of pixels to indent the permission request labels.
61 const int kPermissionIndentSpacing = 12;
63 } // namespace
65 // This class is a MenuButton which is given a PermissionMenuModel. It
66 // shows the current checked item in the menu model, and notifies its listener
67 // about any updates to the state of the selection.
68 // TODO(gbillock): refactor PermissionMenuButton to work like this and re-use?
69 class PermissionCombobox : public views::MenuButton,
70 public views::MenuButtonListener {
71 public:
72 // Get notifications when the selection changes.
73 class Listener {
74 public:
75 virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
78 PermissionCombobox(Listener* listener,
79 int index,
80 const GURL& url,
81 ContentSetting setting);
82 ~PermissionCombobox() override;
84 int index() const { return index_; }
86 void GetAccessibleState(ui::AXViewState* state) override;
88 // MenuButtonListener:
89 void OnMenuButtonClicked(View* source, const gfx::Point& point) override;
91 // Callback when a permission's setting is changed.
92 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo& permission);
94 private:
95 int index_;
96 Listener* listener_;
97 scoped_ptr<PermissionMenuModel> model_;
98 scoped_ptr<views::MenuRunner> menu_runner_;
101 PermissionCombobox::PermissionCombobox(Listener* listener,
102 int index,
103 const GURL& url,
104 ContentSetting setting)
105 : MenuButton(nullptr, base::string16(), this, true),
106 index_(index),
107 listener_(listener),
108 model_(new PermissionMenuModel(
109 url,
110 setting,
111 base::Bind(&PermissionCombobox::PermissionChanged,
112 base::Unretained(this)))) {
113 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(setting)));
114 SizeToPreferredSize();
117 PermissionCombobox::~PermissionCombobox() {}
119 void PermissionCombobox::GetAccessibleState(ui::AXViewState* state) {
120 MenuButton::GetAccessibleState(state);
121 state->value = GetText();
124 void PermissionCombobox::OnMenuButtonClicked(View* source,
125 const gfx::Point& point) {
126 menu_runner_.reset(
127 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
129 gfx::Point p(point);
130 p.Offset(-source->width(), 0);
131 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
132 this,
133 gfx::Rect(p, gfx::Size()),
134 views::MENU_ANCHOR_TOPLEFT,
135 ui::MENU_SOURCE_NONE) ==
136 views::MenuRunner::MENU_DELETED) {
137 return;
141 void PermissionCombobox::PermissionChanged(
142 const WebsiteSettingsUI::PermissionInfo& permission) {
143 SetText(model_->GetLabelAt(model_->GetIndexOfCommandId(permission.setting)));
144 SizeToPreferredSize();
146 listener_->PermissionSelectionChanged(
147 index_, permission.setting == CONTENT_SETTING_ALLOW);
150 ///////////////////////////////////////////////////////////////////////////////
151 // View implementation for the permissions bubble.
152 class PermissionsBubbleDelegateView : public views::BubbleDelegateView,
153 public views::ButtonListener,
154 public PermissionCombobox::Listener {
155 public:
156 PermissionsBubbleDelegateView(
157 views::View* anchor_view,
158 views::BubbleBorder::Arrow anchor_arrow,
159 PermissionBubbleViewViews* owner,
160 const std::string& languages,
161 const std::vector<PermissionBubbleRequest*>& requests,
162 const std::vector<bool>& accept_state);
163 ~PermissionsBubbleDelegateView() override;
165 void Close();
166 void SizeToContents();
168 // BubbleDelegateView:
169 bool ShouldShowCloseButton() const override;
170 bool ShouldShowWindowTitle() const override;
171 const gfx::FontList& GetTitleFontList() const override;
172 base::string16 GetWindowTitle() const override;
173 void OnWidgetDestroying(views::Widget* widget) override;
174 void GetAccessibleState(ui::AXViewState* state) override;
176 // ButtonListener:
177 void ButtonPressed(views::Button* button, const ui::Event& event) override;
179 // PermissionCombobox::Listener:
180 void PermissionSelectionChanged(int index, bool allowed) override;
182 // Updates the anchor's arrow and view. Also repositions the bubble so it's
183 // displayed in the correct location.
184 void UpdateAnchor(views::View* anchor_view,
185 views::BubbleBorder::Arrow anchor_arrow);
187 private:
188 PermissionBubbleViewViews* owner_;
189 views::Button* allow_;
190 views::Button* deny_;
191 base::string16 hostname_;
192 scoped_ptr<PermissionMenuModel> menu_button_model_;
193 std::vector<PermissionCombobox*> customize_comboboxes_;
195 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView);
198 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
199 views::View* anchor_view,
200 views::BubbleBorder::Arrow anchor_arrow,
201 PermissionBubbleViewViews* owner,
202 const std::string& languages,
203 const std::vector<PermissionBubbleRequest*>& requests,
204 const std::vector<bool>& accept_state)
205 : views::BubbleDelegateView(anchor_view, anchor_arrow),
206 owner_(owner),
207 allow_(nullptr),
208 deny_(nullptr) {
209 DCHECK(!requests.empty());
211 RemoveAllChildViews(true);
212 customize_comboboxes_.clear();
213 set_close_on_esc(true);
214 set_close_on_deactivate(false);
216 SetLayoutManager(new views::BoxLayout(
217 views::BoxLayout::kVertical, kBubbleOuterMargin, 0, kItemMajorSpacing));
219 hostname_ = url_formatter::FormatUrlForSecurityDisplay(
220 requests[0]->GetRequestingHostname(), languages);
222 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
223 for (size_t index = 0; index < requests.size(); index++) {
224 DCHECK(index < accept_state.size());
225 // The row is laid out containing a leading-aligned label area and a
226 // trailing column which will be filled if there are multiple permission
227 // requests.
228 views::View* row = new views::View();
229 views::GridLayout* row_layout = new views::GridLayout(row);
230 row->SetLayoutManager(row_layout);
231 views::ColumnSet* columns = row_layout->AddColumnSet(0);
232 columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
233 0, views::GridLayout::USE_PREF, 0, 0);
234 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
235 100, views::GridLayout::USE_PREF, 0, 0);
236 row_layout->StartRow(0, 0);
238 views::View* label_container = new views::View();
239 label_container->SetLayoutManager(
240 new views::BoxLayout(views::BoxLayout::kHorizontal,
241 kPermissionIndentSpacing,
242 0, kBubbleOuterMargin));
243 views::ImageView* icon = new views::ImageView();
244 gfx::VectorIconId vector_id = requests[index]->GetVectorIconId();
245 if (vector_id != gfx::VectorIconId::VECTOR_ICON_NONE) {
246 SkColor grey;
247 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey,
248 &grey);
249 icon->SetImage(gfx::CreateVectorIcon(vector_id, kIconSize, grey));
250 } else {
251 icon->SetImage(bundle.GetImageSkiaNamed(requests.at(index)->GetIconId()));
252 icon->SetImageSize(gfx::Size(kIconSize, kIconSize));
254 icon->SetTooltipText(base::string16()); // Redundant with the text fragment
255 label_container->AddChildView(icon);
256 views::Label* label =
257 new views::Label(requests.at(index)->GetMessageTextFragment());
258 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
259 label_container->AddChildView(label);
260 row_layout->AddView(label_container);
262 if (requests.size() > 1) {
263 PermissionCombobox* combobox = new PermissionCombobox(
264 this,
265 index,
266 requests[index]->GetRequestingHostname(),
267 accept_state[index] ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
268 row_layout->AddView(combobox);
269 customize_comboboxes_.push_back(combobox);
270 } else {
271 row_layout->AddView(new views::View());
274 AddChildView(row);
277 views::View* button_row = new views::View();
278 views::GridLayout* button_layout = new views::GridLayout(button_row);
279 views::ColumnSet* columns = button_layout->AddColumnSet(0);
280 button_row->SetLayoutManager(button_layout);
281 AddChildView(button_row);
283 // For multiple permissions: just an "OK" button.
284 if (requests.size() > 1) {
285 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
286 100, views::GridLayout::USE_PREF, 0, 0);
287 button_layout->StartRowWithPadding(0, 0, 0, 4);
288 views::LabelButton* ok_button =
289 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK));
290 ok_button->SetStyle(views::Button::STYLE_BUTTON);
291 button_layout->AddView(ok_button);
292 allow_ = ok_button;
294 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
295 return;
298 // For a single permission: lay out the Deny/Allow buttons.
299 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
300 100, views::GridLayout::USE_PREF, 0, 0);
301 columns->AddPaddingColumn(0, kItemMajorSpacing - (2*kButtonBorderSize));
302 columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::FILL,
303 0, views::GridLayout::USE_PREF, 0, 0);
304 button_layout->StartRow(0, 0);
306 base::string16 allow_text = l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW);
307 views::LabelButton* allow_button = new views::LabelButton(this, allow_text);
308 allow_button->SetStyle(views::Button::STYLE_BUTTON);
309 button_layout->AddView(allow_button);
310 allow_ = allow_button;
312 base::string16 deny_text = l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
313 views::LabelButton* deny_button = new views::LabelButton(this, deny_text);
314 deny_button->SetStyle(views::Button::STYLE_BUTTON);
315 button_layout->AddView(deny_button);
316 deny_ = deny_button;
318 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
321 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
322 if (owner_)
323 owner_->Closing();
326 void PermissionsBubbleDelegateView::Close() {
327 owner_ = nullptr;
328 GetWidget()->Close();
331 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
332 return true;
335 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
336 return true;
339 const gfx::FontList& PermissionsBubbleDelegateView::GetTitleFontList() const {
340 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
341 return rb.GetFontList(ui::ResourceBundle::BaseFont);
344 base::string16 PermissionsBubbleDelegateView::GetWindowTitle() const {
345 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT,
346 hostname_);
349 void PermissionsBubbleDelegateView::SizeToContents() {
350 BubbleDelegateView::SizeToContents();
353 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
354 views::BubbleDelegateView::OnWidgetDestroying(widget);
355 if (owner_) {
356 owner_->Closing();
357 owner_ = nullptr;
361 void PermissionsBubbleDelegateView::GetAccessibleState(ui::AXViewState* state) {
362 views::BubbleDelegateView::GetAccessibleState(state);
363 state->role = ui::AX_ROLE_ALERT_DIALOG;
366 void PermissionsBubbleDelegateView::ButtonPressed(views::Button* button,
367 const ui::Event& event) {
368 if (!owner_)
369 return;
371 if (button == allow_)
372 owner_->Accept();
373 else if (button == deny_)
374 owner_->Deny();
377 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
378 int index, bool allowed) {
379 owner_->Toggle(index, allowed);
382 void PermissionsBubbleDelegateView::UpdateAnchor(
383 views::View* anchor_view,
384 views::BubbleBorder::Arrow anchor_arrow) {
385 if (GetAnchorView() == anchor_view && arrow() == anchor_arrow)
386 return;
388 set_arrow(anchor_arrow);
390 // Update the border in the bubble: will either add or remove the arrow.
391 views::BubbleFrameView* frame =
392 views::BubbleDelegateView::GetBubbleFrameView();
393 views::BubbleBorder::Arrow adjusted_arrow = anchor_arrow;
394 if (base::i18n::IsRTL())
395 adjusted_arrow = views::BubbleBorder::horizontal_mirror(adjusted_arrow);
396 frame->SetBubbleBorder(scoped_ptr<views::BubbleBorder>(
397 new views::BubbleBorder(adjusted_arrow, shadow(), color())));
399 // Reposition the bubble based on the updated arrow and view.
400 SetAnchorView(anchor_view);
403 //////////////////////////////////////////////////////////////////////////////
404 // PermissionBubbleViewViews
406 PermissionBubbleViewViews::PermissionBubbleViewViews(Browser* browser)
407 : browser_(browser),
408 delegate_(nullptr),
409 bubble_delegate_(nullptr) {
410 DCHECK(browser);
413 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
416 // static
417 scoped_ptr<PermissionBubbleView> PermissionBubbleView::Create(
418 Browser* browser) {
419 return make_scoped_ptr(new PermissionBubbleViewViews(browser));
422 views::View* PermissionBubbleViewViews::GetAnchorView() {
423 BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser_);
425 if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR))
426 return browser_view->GetLocationBarView()->location_icon_view();
428 if (browser_view->IsFullscreenBubbleVisible())
429 return browser_view->exclusive_access_bubble()->GetView();
431 return browser_view->top_container();
434 views::BubbleBorder::Arrow PermissionBubbleViewViews::GetAnchorArrow() {
435 if (browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR))
436 return views::BubbleBorder::TOP_LEFT;
437 return views::BubbleBorder::NONE;
440 void PermissionBubbleViewViews::SetDelegate(Delegate* delegate) {
441 delegate_ = delegate;
444 void PermissionBubbleViewViews::Show(
445 const std::vector<PermissionBubbleRequest*>& requests,
446 const std::vector<bool>& values) {
447 if (bubble_delegate_)
448 bubble_delegate_->Close();
450 bubble_delegate_ =
451 new PermissionsBubbleDelegateView(
452 GetAnchorView(), GetAnchorArrow(), this,
453 browser_->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages),
454 requests, values);
456 // Set |parent_window| because some valid anchors can become hidden.
457 views::Widget* widget = views::Widget::GetWidgetForNativeWindow(
458 browser_->window()->GetNativeWindow());
459 bubble_delegate_->set_parent_window(widget->GetNativeView());
461 views::BubbleDelegateView::CreateBubble(bubble_delegate_)->Show();
462 bubble_delegate_->SizeToContents();
465 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
466 return !(bubble_delegate_ && bubble_delegate_->IsMouseHovered());
469 void PermissionBubbleViewViews::Hide() {
470 if (bubble_delegate_) {
471 bubble_delegate_->Close();
472 bubble_delegate_ = nullptr;
476 bool PermissionBubbleViewViews::IsVisible() {
477 return bubble_delegate_ != nullptr;
480 void PermissionBubbleViewViews::UpdateAnchorPosition() {
481 if (IsVisible())
482 bubble_delegate_->UpdateAnchor(GetAnchorView(), GetAnchorArrow());
485 gfx::NativeWindow PermissionBubbleViewViews::GetNativeWindow() {
486 if (bubble_delegate_ && bubble_delegate_->GetWidget())
487 return bubble_delegate_->GetWidget()->GetNativeWindow();
488 return nullptr;
491 void PermissionBubbleViewViews::Closing() {
492 if (bubble_delegate_)
493 bubble_delegate_ = nullptr;
494 if (delegate_)
495 delegate_->Closing();
498 void PermissionBubbleViewViews::Toggle(int index, bool value) {
499 if (delegate_)
500 delegate_->ToggleAccept(index, value);
503 void PermissionBubbleViewViews::Accept() {
504 if (delegate_)
505 delegate_->Accept();
508 void PermissionBubbleViewViews::Deny() {
509 if (delegate_)
510 delegate_->Deny();