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 "net/base/net_util.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/text_constants.h"
27 #include "ui/views/bubble/bubble_delegate.h"
28 #include "ui/views/bubble/bubble_frame_view.h"
29 #include "ui/views/controls/button/checkbox.h"
30 #include "ui/views/controls/button/label_button.h"
31 #include "ui/views/controls/button/label_button_border.h"
32 #include "ui/views/controls/button/menu_button.h"
33 #include "ui/views/controls/button/menu_button_listener.h"
34 #include "ui/views/controls/combobox/combobox.h"
35 #include "ui/views/controls/combobox/combobox_listener.h"
36 #include "ui/views/controls/label.h"
37 #include "ui/views/controls/menu/menu_runner.h"
38 #include "ui/views/layout/box_layout.h"
39 #include "ui/views/layout/grid_layout.h"
43 // Spacing constant for outer margin. This is added to the
44 // bubble margin itself to equalize the margins at 13px.
45 const int kBubbleOuterMargin
= 5;
47 // Spacing between major items should be 9px.
48 const int kItemMajorSpacing
= 9;
50 // Button border size, draws inside the spacing distance.
51 const int kButtonBorderSize
= 2;
53 // (Square) pixel size of icon.
54 const int kIconSize
= 18;
56 // Number of pixels to indent the permission request labels.
57 const int kPermissionIndentSpacing
= 12;
61 // This class is a MenuButton which is given a PermissionMenuModel. It
62 // shows the current checked item in the menu model, and notifies its listener
63 // about any updates to the state of the selection.
64 // TODO: refactor PermissionMenuButton to work like this and re-use?
65 class PermissionCombobox
: public views::MenuButton
,
66 public views::MenuButtonListener
{
68 // Get notifications when the selection changes.
71 virtual void PermissionSelectionChanged(int index
, bool allowed
) = 0;
74 PermissionCombobox(Listener
* listener
,
77 ContentSetting setting
);
78 ~PermissionCombobox() override
;
80 int index() const { return index_
; }
82 void GetAccessibleState(ui::AXViewState
* state
) override
;
84 // MenuButtonListener:
85 void OnMenuButtonClicked(View
* source
, const gfx::Point
& point
) override
;
87 // Callback when a permission's setting is changed.
88 void PermissionChanged(const WebsiteSettingsUI::PermissionInfo
& permission
);
93 scoped_ptr
<PermissionMenuModel
> model_
;
94 scoped_ptr
<views::MenuRunner
> menu_runner_
;
97 PermissionCombobox::PermissionCombobox(Listener
* listener
,
100 ContentSetting setting
)
101 : MenuButton(nullptr, base::string16(), this, true),
104 model_(new PermissionMenuModel(
107 base::Bind(&PermissionCombobox::PermissionChanged
,
108 base::Unretained(this)))) {
109 SetText(model_
->GetLabelAt(model_
->GetIndexOfCommandId(setting
)));
110 SizeToPreferredSize();
113 PermissionCombobox::~PermissionCombobox() {}
115 void PermissionCombobox::GetAccessibleState(ui::AXViewState
* state
) {
116 MenuButton::GetAccessibleState(state
);
117 state
->value
= GetText();
120 void PermissionCombobox::OnMenuButtonClicked(View
* source
,
121 const gfx::Point
& point
) {
123 new views::MenuRunner(model_
.get(), views::MenuRunner::HAS_MNEMONICS
));
126 p
.Offset(-source
->width(), 0);
127 if (menu_runner_
->RunMenuAt(source
->GetWidget()->GetTopLevelWidget(),
129 gfx::Rect(p
, gfx::Size()),
130 views::MENU_ANCHOR_TOPLEFT
,
131 ui::MENU_SOURCE_NONE
) ==
132 views::MenuRunner::MENU_DELETED
) {
137 void PermissionCombobox::PermissionChanged(
138 const WebsiteSettingsUI::PermissionInfo
& permission
) {
139 SetText(model_
->GetLabelAt(model_
->GetIndexOfCommandId(permission
.setting
)));
140 SizeToPreferredSize();
142 listener_
->PermissionSelectionChanged(
143 index_
, permission
.setting
== CONTENT_SETTING_ALLOW
);
146 ///////////////////////////////////////////////////////////////////////////////
147 // View implementation for the permissions bubble.
148 class PermissionsBubbleDelegateView
: public views::BubbleDelegateView
,
149 public views::ButtonListener
,
150 public PermissionCombobox::Listener
{
152 PermissionsBubbleDelegateView(
153 views::View
* anchor_view
,
154 views::BubbleBorder::Arrow anchor_arrow
,
155 PermissionBubbleViewViews
* owner
,
156 const std::string
& languages
,
157 const std::vector
<PermissionBubbleRequest
*>& requests
,
158 const std::vector
<bool>& accept_state
);
159 ~PermissionsBubbleDelegateView() override
;
162 void SizeToContents();
164 // BubbleDelegateView:
165 bool ShouldShowCloseButton() const override
;
166 bool ShouldShowWindowTitle() const override
;
167 const gfx::FontList
& GetTitleFontList() const override
;
168 base::string16
GetWindowTitle() const override
;
169 void OnWidgetDestroying(views::Widget
* widget
) override
;
170 void GetAccessibleState(ui::AXViewState
* state
) override
;
173 void ButtonPressed(views::Button
* button
, const ui::Event
& event
) override
;
175 // PermissionCombobox::Listener:
176 void PermissionSelectionChanged(int index
, bool allowed
) override
;
178 // Updates the anchor's arrow and view. Also repositions the bubble so it's
179 // displayed in the correct location.
180 void UpdateAnchor(views::View
* anchor_view
,
181 views::BubbleBorder::Arrow anchor_arrow
);
184 PermissionBubbleViewViews
* owner_
;
185 views::Button
* allow_
;
186 views::Button
* deny_
;
187 base::string16 hostname_
;
188 scoped_ptr
<PermissionMenuModel
> menu_button_model_
;
189 std::vector
<PermissionCombobox
*> customize_comboboxes_
;
191 DISALLOW_COPY_AND_ASSIGN(PermissionsBubbleDelegateView
);
194 PermissionsBubbleDelegateView::PermissionsBubbleDelegateView(
195 views::View
* anchor_view
,
196 views::BubbleBorder::Arrow anchor_arrow
,
197 PermissionBubbleViewViews
* owner
,
198 const std::string
& languages
,
199 const std::vector
<PermissionBubbleRequest
*>& requests
,
200 const std::vector
<bool>& accept_state
)
201 : views::BubbleDelegateView(anchor_view
, anchor_arrow
),
205 DCHECK(!requests
.empty());
207 RemoveAllChildViews(true);
208 customize_comboboxes_
.clear();
209 set_close_on_esc(true);
210 set_close_on_deactivate(false);
212 SetLayoutManager(new views::BoxLayout(
213 views::BoxLayout::kVertical
, kBubbleOuterMargin
, 0, kItemMajorSpacing
));
215 hostname_
= net::FormatUrl(requests
[0]->GetRequestingHostname(),
217 net::kFormatUrlOmitUsernamePassword
|
218 net::kFormatUrlOmitTrailingSlashOnBareHostname
,
219 net::UnescapeRule::SPACES
,
220 nullptr, nullptr, nullptr);
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
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 icon
->SetImage(bundle
.GetImageSkiaNamed(requests
.at(index
)->GetIconID()));
245 icon
->SetImageSize(gfx::Size(kIconSize
, kIconSize
));
246 icon
->SetTooltipText(base::string16()); // Redundant with the text fragment
247 label_container
->AddChildView(icon
);
248 views::Label
* label
=
249 new views::Label(requests
.at(index
)->GetMessageTextFragment());
250 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
251 label_container
->AddChildView(label
);
252 row_layout
->AddView(label_container
);
254 if (requests
.size() > 1) {
255 PermissionCombobox
* combobox
= new PermissionCombobox(
258 requests
[index
]->GetRequestingHostname(),
259 accept_state
[index
] ? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
);
260 row_layout
->AddView(combobox
);
261 customize_comboboxes_
.push_back(combobox
);
263 row_layout
->AddView(new views::View());
269 views::View
* button_row
= new views::View();
270 views::GridLayout
* button_layout
= new views::GridLayout(button_row
);
271 views::ColumnSet
* columns
= button_layout
->AddColumnSet(0);
272 button_row
->SetLayoutManager(button_layout
);
273 AddChildView(button_row
);
275 // For multiple permissions: just an "OK" button.
276 if (requests
.size() > 1) {
277 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
278 100, views::GridLayout::USE_PREF
, 0, 0);
279 button_layout
->StartRowWithPadding(0, 0, 0, 4);
280 views::LabelButton
* ok_button
=
281 new views::LabelButton(this, l10n_util::GetStringUTF16(IDS_OK
));
282 ok_button
->SetStyle(views::Button::STYLE_BUTTON
);
283 button_layout
->AddView(ok_button
);
286 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
290 // For a single permission: lay out the Deny/Allow buttons.
291 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
292 100, views::GridLayout::USE_PREF
, 0, 0);
293 columns
->AddPaddingColumn(0, kItemMajorSpacing
- (2*kButtonBorderSize
));
294 columns
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::FILL
,
295 0, views::GridLayout::USE_PREF
, 0, 0);
296 button_layout
->StartRow(0, 0);
298 base::string16 allow_text
= l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW
);
299 views::LabelButton
* allow_button
= new views::LabelButton(this, allow_text
);
300 allow_button
->SetStyle(views::Button::STYLE_BUTTON
);
301 button_layout
->AddView(allow_button
);
302 allow_
= allow_button
;
304 base::string16 deny_text
= l10n_util::GetStringUTF16(IDS_PERMISSION_DENY
);
305 views::LabelButton
* deny_button
= new views::LabelButton(this, deny_text
);
306 deny_button
->SetStyle(views::Button::STYLE_BUTTON
);
307 button_layout
->AddView(deny_button
);
310 button_layout
->AddPaddingRow(0, kBubbleOuterMargin
);
313 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
318 void PermissionsBubbleDelegateView::Close() {
320 GetWidget()->Close();
323 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
327 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
331 const gfx::FontList
& PermissionsBubbleDelegateView::GetTitleFontList() const {
332 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
333 return rb
.GetFontList(ui::ResourceBundle::BaseFont
);
336 base::string16
PermissionsBubbleDelegateView::GetWindowTitle() const {
337 return l10n_util::GetStringFUTF16(IDS_PERMISSIONS_BUBBLE_PROMPT
,
341 void PermissionsBubbleDelegateView::SizeToContents() {
342 BubbleDelegateView::SizeToContents();
345 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget
* widget
) {
346 views::BubbleDelegateView::OnWidgetDestroying(widget
);
353 void PermissionsBubbleDelegateView::GetAccessibleState(ui::AXViewState
* state
) {
354 views::BubbleDelegateView::GetAccessibleState(state
);
355 state
->role
= ui::AX_ROLE_ALERT_DIALOG
;
358 void PermissionsBubbleDelegateView::ButtonPressed(views::Button
* button
,
359 const ui::Event
& event
) {
363 if (button
== allow_
)
365 else if (button
== deny_
)
369 void PermissionsBubbleDelegateView::PermissionSelectionChanged(
370 int index
, bool allowed
) {
371 owner_
->Toggle(index
, allowed
);
374 void PermissionsBubbleDelegateView::UpdateAnchor(
375 views::View
* anchor_view
,
376 views::BubbleBorder::Arrow anchor_arrow
) {
377 if (GetAnchorView() == anchor_view
&& arrow() == anchor_arrow
)
380 set_arrow(anchor_arrow
);
382 // Update the border in the bubble: will either add or remove the arrow.
383 views::BubbleFrameView
* frame
=
384 views::BubbleDelegateView::GetBubbleFrameView();
385 views::BubbleBorder::Arrow adjusted_arrow
= anchor_arrow
;
386 if (base::i18n::IsRTL())
387 adjusted_arrow
= views::BubbleBorder::horizontal_mirror(adjusted_arrow
);
388 frame
->SetBubbleBorder(scoped_ptr
<views::BubbleBorder
>(
389 new views::BubbleBorder(adjusted_arrow
, shadow(), color())));
391 // Reposition the bubble based on the updated arrow and view.
392 SetAnchorView(anchor_view
);
395 //////////////////////////////////////////////////////////////////////////////
396 // PermissionBubbleViewViews
398 PermissionBubbleViewViews::PermissionBubbleViewViews(Browser
* browser
)
401 bubble_delegate_(nullptr) {}
403 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
405 delegate_
->SetView(nullptr);
408 views::View
* PermissionBubbleViewViews::GetAnchorView() {
409 BrowserView
* browser_view
= BrowserView::GetBrowserViewForBrowser(browser_
);
411 if (browser_
->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR
))
412 return browser_view
->GetLocationBarView()->location_icon_view();
414 if (browser_view
->IsFullscreenBubbleVisible())
415 return browser_view
->exclusive_access_bubble()->GetView();
417 return browser_view
->top_container();
420 views::BubbleBorder::Arrow
PermissionBubbleViewViews::GetAnchorArrow() {
421 if (browser_
->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR
))
422 return views::BubbleBorder::TOP_LEFT
;
423 return views::BubbleBorder::NONE
;
426 void PermissionBubbleViewViews::UpdateAnchorPosition() {
428 bubble_delegate_
->UpdateAnchor(GetAnchorView(), GetAnchorArrow());
431 void PermissionBubbleViewViews::SetDelegate(Delegate
* delegate
) {
432 delegate_
= delegate
;
435 void PermissionBubbleViewViews::Show(
436 const std::vector
<PermissionBubbleRequest
*>& requests
,
437 const std::vector
<bool>& values
) {
438 if (bubble_delegate_
)
439 bubble_delegate_
->Close();
442 new PermissionsBubbleDelegateView(
443 GetAnchorView(), GetAnchorArrow(), this,
444 browser_
->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages
),
447 // Set |parent_window| because some valid anchors can become hidden.
448 views::Widget
* widget
= views::Widget::GetWidgetForNativeWindow(
449 browser_
->window()->GetNativeWindow());
450 bubble_delegate_
->set_parent_window(widget
->GetNativeView());
452 views::BubbleDelegateView::CreateBubble(bubble_delegate_
)->Show();
453 bubble_delegate_
->SizeToContents();
456 bool PermissionBubbleViewViews::CanAcceptRequestUpdate() {
457 return !(bubble_delegate_
&& bubble_delegate_
->IsMouseHovered());
460 void PermissionBubbleViewViews::Hide() {
461 if (bubble_delegate_
) {
462 bubble_delegate_
->Close();
463 bubble_delegate_
= nullptr;
467 bool PermissionBubbleViewViews::IsVisible() {
468 return bubble_delegate_
!= nullptr;
471 void PermissionBubbleViewViews::Closing() {
472 if (bubble_delegate_
)
473 bubble_delegate_
= nullptr;
475 delegate_
->Closing();
478 void PermissionBubbleViewViews::Toggle(int index
, bool value
) {
480 delegate_
->ToggleAccept(index
, value
);
483 void PermissionBubbleViewViews::Accept() {
488 void PermissionBubbleViewViews::Deny() {