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/url_formatter.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"
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;
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
{
72 // Get notifications when the selection changes.
75 virtual void PermissionSelectionChanged(int index
, bool allowed
) = 0;
78 PermissionCombobox(Listener
* listener
,
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
);
97 scoped_ptr
<PermissionMenuModel
> model_
;
98 scoped_ptr
<views::MenuRunner
> menu_runner_
;
101 PermissionCombobox::PermissionCombobox(Listener
* listener
,
104 ContentSetting setting
)
105 : MenuButton(nullptr, base::string16(), this, true),
108 model_(new PermissionMenuModel(
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
) {
127 new views::MenuRunner(model_
.get(), views::MenuRunner::HAS_MNEMONICS
));
130 p
.Offset(-source
->width(), 0);
131 if (menu_runner_
->RunMenuAt(source
->GetWidget()->GetTopLevelWidget(),
133 gfx::Rect(p
, gfx::Size()),
134 views::MENU_ANCHOR_TOPLEFT
,
135 ui::MENU_SOURCE_NONE
) ==
136 views::MenuRunner::MENU_DELETED
) {
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
{
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
;
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
;
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
);
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
),
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::FormatUrl(
220 requests
[0]->GetRequestingHostname(), languages
,
221 url_formatter::kFormatUrlOmitUsernamePassword
|
222 url_formatter::kFormatUrlOmitTrailingSlashOnBareHostname
,
223 net::UnescapeRule::SPACES
, nullptr, nullptr, nullptr);
225 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
226 for (size_t index
= 0; index
< requests
.size(); index
++) {
227 DCHECK(index
< accept_state
.size());
228 // The row is laid out containing a leading-aligned label area and a
229 // trailing column which will be filled if there are multiple permission
231 views::View
* row
= new views::View();
232 views::GridLayout
* row_layout
= new views::GridLayout(row
);
233 row
->SetLayoutManager(row_layout
);
234 views::ColumnSet
* columns
= row_layout
->AddColumnSet(0);
235 columns
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::FILL
,
236 0, views::GridLayout::USE_PREF
, 0, 0);
237 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
238 100, views::GridLayout::USE_PREF
, 0, 0);
239 row_layout
->StartRow(0, 0);
241 views::View
* label_container
= new views::View();
242 label_container
->SetLayoutManager(
243 new views::BoxLayout(views::BoxLayout::kHorizontal
,
244 kPermissionIndentSpacing
,
245 0, kBubbleOuterMargin
));
246 views::ImageView
* icon
= new views::ImageView();
247 gfx::VectorIconId vector_id
= requests
[index
]->GetVectorIconId();
248 if (vector_id
!= gfx::VectorIconId::VECTOR_ICON_NONE
) {
250 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey
,
252 icon
->SetImage(gfx::CreateVectorIcon(vector_id
, kIconSize
, grey
));
254 icon
->SetImage(bundle
.GetImageSkiaNamed(requests
.at(index
)->GetIconId()));
255 icon
->SetImageSize(gfx::Size(kIconSize
, kIconSize
));
257 icon
->SetTooltipText(base::string16()); // Redundant with the text fragment
258 label_container
->AddChildView(icon
);
259 views::Label
* label
=
260 new views::Label(requests
.at(index
)->GetMessageTextFragment());
261 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
262 label_container
->AddChildView(label
);
263 row_layout
->AddView(label_container
);
265 if (requests
.size() > 1) {
266 PermissionCombobox
* combobox
= new PermissionCombobox(
269 requests
[index
]->GetRequestingHostname(),
270 accept_state
[index
] ? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
);
271 row_layout
->AddView(combobox
);
272 customize_comboboxes_
.push_back(combobox
);
274 row_layout
->AddView(new views::View());
280 views::View
* button_row
= new views::View();
281 views::GridLayout
* button_layout
= new views::GridLayout(button_row
);
282 views::ColumnSet
* columns
= button_layout
->AddColumnSet(0);
283 button_row
->SetLayoutManager(button_layout
);
284 AddChildView(button_row
);
286 // For multiple permissions: just an "OK" button.
287 if (requests
.size() > 1) {
288 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
289 100, views::GridLayout::USE_PREF
, 0, 0);
290 button_layout
->StartRowWithPadding(0, 0, 0, 4);
291 views::LabelButton
* ok_button
=
292 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK
));
293 ok_button
->SetStyle(views::Button::STYLE_BUTTON
);
294 button_layout
->AddView(ok_button
);
297 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
301 // For a single permission: lay out the Deny/Allow buttons.
302 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
303 100, views::GridLayout::USE_PREF
, 0, 0);
304 columns
->AddPaddingColumn(0, kItemMajorSpacing
- (2*kButtonBorderSize
));
305 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
306 0, views::GridLayout::USE_PREF
, 0, 0);
307 button_layout
->StartRow(0, 0);
309 base::string16 allow_text
= l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW
);
310 views::LabelButton
* allow_button
= new views::LabelButton(this, allow_text
);
311 allow_button
->SetStyle(views::Button::STYLE_BUTTON
);
312 button_layout
->AddView(allow_button
);
313 allow_
= allow_button
;
315 base::string16 deny_text
= l10n_util::GetStringUTF16(IDS_PERMISSION_DENY
);
316 views::LabelButton
* deny_button
= new views::LabelButton(this, deny_text
);
317 deny_button
->SetStyle(views::Button::STYLE_BUTTON
);
318 button_layout
->AddView(deny_button
);
321 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
324 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
329 void PermissionsBubbleDelegateView::Close() {
331 GetWidget()->Close();
334 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
338 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
342 const gfx::FontList
& PermissionsBubbleDelegateView::GetTitleFontList() const {
343 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
344 return rb
.GetFontList(ui::ResourceBundle::BaseFont
);
347 base::string16
PermissionsBubbleDelegateView::GetWindowTitle() const {
348 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT
,
352 void PermissionsBubbleDelegateView::SizeToContents() {
353 BubbleDelegateView::SizeToContents();
356 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget
* widget
) {
357 views::BubbleDelegateView::OnWidgetDestroying(widget
);
364 void PermissionsBubbleDelegateView::GetAccessibleState(ui::AXViewState
* state
) {
365 views::BubbleDelegateView::GetAccessibleState(state
);
366 state
->role
= ui::AX_ROLE_ALERT_DIALOG
;
369 void PermissionsBubbleDelegateView::ButtonPressed(views::Button
* button
,
370 const ui::Event
& event
) {
374 if (button
== allow_
)
376 else if (button
== deny_
)
380 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
381 int index
, bool allowed
) {
382 owner_
->Toggle(index
, allowed
);
385 void PermissionsBubbleDelegateView::UpdateAnchor(
386 views::View
* anchor_view
,
387 views::BubbleBorder::Arrow anchor_arrow
) {
388 if (GetAnchorView() == anchor_view
&& arrow() == anchor_arrow
)
391 set_arrow(anchor_arrow
);
393 // Update the border in the bubble: will either add or remove the arrow.
394 views::BubbleFrameView
* frame
=
395 views::BubbleDelegateView::GetBubbleFrameView();
396 views::BubbleBorder::Arrow adjusted_arrow
= anchor_arrow
;
397 if (base::i18n::IsRTL())
398 adjusted_arrow
= views::BubbleBorder::horizontal_mirror(adjusted_arrow
);
399 frame
->SetBubbleBorder(scoped_ptr
<views::BubbleBorder
>(
400 new views::BubbleBorder(adjusted_arrow
, shadow(), color())));
402 // Reposition the bubble based on the updated arrow and view.
403 SetAnchorView(anchor_view
);
406 //////////////////////////////////////////////////////////////////////////////
407 // PermissionBubbleViewViews
409 PermissionBubbleViewViews::PermissionBubbleViewViews(Browser
* browser
)
412 bubble_delegate_(nullptr) {
416 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
420 scoped_ptr
<PermissionBubbleView
> PermissionBubbleView::Create(
422 return make_scoped_ptr(new PermissionBubbleViewViews(browser
));
425 views::View
* PermissionBubbleViewViews::GetAnchorView() {
426 BrowserView
* browser_view
= BrowserView::GetBrowserViewForBrowser(browser_
);
428 if (browser_
->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR
))
429 return browser_view
->GetLocationBarView()->location_icon_view();
431 if (browser_view
->IsFullscreenBubbleVisible())
432 return browser_view
->exclusive_access_bubble()->GetView();
434 return browser_view
->top_container();
437 views::BubbleBorder::Arrow
PermissionBubbleViewViews::GetAnchorArrow() {
438 if (browser_
->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR
))
439 return views::BubbleBorder::TOP_LEFT
;
440 return views::BubbleBorder::NONE
;
443 void PermissionBubbleViewViews::SetDelegate(Delegate
* delegate
) {
444 delegate_
= delegate
;
447 void PermissionBubbleViewViews::Show(
448 const std::vector
<PermissionBubbleRequest
*>& requests
,
449 const std::vector
<bool>& values
) {
450 if (bubble_delegate_
)
451 bubble_delegate_
->Close();
454 new PermissionsBubbleDelegateView(
455 GetAnchorView(), GetAnchorArrow(), this,
456 browser_
->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages
),
459 // Set |parent_window| because some valid anchors can become hidden.
460 views::Widget
* widget
= views::Widget::GetWidgetForNativeWindow(
461 browser_
->window()->GetNativeWindow());
462 bubble_delegate_
->set_parent_window(widget
->GetNativeView());
464 views::BubbleDelegateView::CreateBubble(bubble_delegate_
)->Show();
465 bubble_delegate_
->SizeToContents();
468 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
469 return !(bubble_delegate_
&& bubble_delegate_
->IsMouseHovered());
472 void PermissionBubbleViewViews::Hide() {
473 if (bubble_delegate_
) {
474 bubble_delegate_
->Close();
475 bubble_delegate_
= nullptr;
479 bool PermissionBubbleViewViews::IsVisible() {
480 return bubble_delegate_
!= nullptr;
483 void PermissionBubbleViewViews::UpdateAnchorPosition() {
485 bubble_delegate_
->UpdateAnchor(GetAnchorView(), GetAnchorArrow());
488 gfx::NativeWindow
PermissionBubbleViewViews::GetNativeWindow() {
489 if (bubble_delegate_
&& bubble_delegate_
->GetWidget())
490 return bubble_delegate_
->GetWidget()->GetNativeWindow();
494 void PermissionBubbleViewViews::Closing() {
495 if (bubble_delegate_
)
496 bubble_delegate_
= nullptr;
498 delegate_
->Closing();
501 void PermissionBubbleViewViews::Toggle(int index
, bool value
) {
503 delegate_
->ToggleAccept(index
, value
);
506 void PermissionBubbleViewViews::Accept() {
511 void PermissionBubbleViewViews::Deny() {