Reland: Add an event to notify accessibility when a permissions bubble gets shown.
[chromium-blink-merge.git] / chrome / browser / ui / views / website_settings / permissions_bubble_view.cc
blobfcfdf1393f98543ceeffcbe83c642c91a8aa5672
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"
41 namespace {
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;
59 } // namespace
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 {
67 public:
68 // Get notifications when the selection changes.
69 class Listener {
70 public:
71 virtual void PermissionSelectionChanged(int index, bool allowed) = 0;
74 PermissionCombobox(Listener* listener,
75 int index,
76 const GURL& url,
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);
90 private:
91 int index_;
92 Listener* listener_;
93 scoped_ptr<PermissionMenuModel> model_;
94 scoped_ptr<views::MenuRunner> menu_runner_;
97 PermissionCombobox::PermissionCombobox(Listener* listener,
98 int index,
99 const GURL& url,
100 ContentSetting setting)
101 : MenuButton(nullptr, base::string16(), this, true),
102 index_(index),
103 listener_(listener),
104 model_(new PermissionMenuModel(
105 url,
106 setting,
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) {
122 menu_runner_.reset(
123 new views::MenuRunner(model_.get(), views::MenuRunner::HAS_MNEMONICS));
125 gfx::Point p(point);
126 p.Offset(-source->width(), 0);
127 if (menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(),
128 this,
129 gfx::Rect(p, gfx::Size()),
130 views::MENU_ANCHOR_TOPLEFT,
131 ui::MENU_SOURCE_NONE) ==
132 views::MenuRunner::MENU_DELETED) {
133 return;
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 {
151 public:
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;
161 void Close();
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;
172 // ButtonListener:
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);
183 private:
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),
202 owner_(owner),
203 allow_(nullptr),
204 deny_(nullptr) {
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(),
216 languages,
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
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 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(
256 this,
257 index,
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);
262 } else {
263 row_layout->AddView(new views::View());
266 AddChildView(row);
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);
284 allow_ = ok_button;
286 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
287 return;
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);
308 deny_ = deny_button;
310 button_layout->AddPaddingRow(0, kBubbleOuterMargin);
313 PermissionsBubbleDelegateView::~PermissionsBubbleDelegateView() {
314 if (owner_)
315 owner_->Closing();
318 void PermissionsBubbleDelegateView::Close() {
319 owner_ = nullptr;
320 GetWidget()->Close();
323 bool PermissionsBubbleDelegateView::ShouldShowCloseButton() const {
324 return true;
327 bool PermissionsBubbleDelegateView::ShouldShowWindowTitle() const {
328 return true;
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,
338 hostname_);
341 void PermissionsBubbleDelegateView::SizeToContents() {
342 BubbleDelegateView::SizeToContents();
345 void PermissionsBubbleDelegateView::OnWidgetDestroying(views::Widget* widget) {
346 views::BubbleDelegateView::OnWidgetDestroying(widget);
347 if (owner_) {
348 owner_->Closing();
349 owner_ = nullptr;
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) {
360 if (!owner_)
361 return;
363 if (button == allow_)
364 owner_->Accept();
365 else if (button == deny_)
366 owner_->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)
378 return;
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)
399 : browser_(browser),
400 delegate_(nullptr),
401 bubble_delegate_(nullptr) {}
403 PermissionBubbleViewViews::~PermissionBubbleViewViews() {
404 if (delegate_)
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() {
427 if (IsVisible())
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();
441 bubble_delegate_ =
442 new PermissionsBubbleDelegateView(
443 GetAnchorView(), GetAnchorArrow(), this,
444 browser_->profile()->GetPrefs()->GetString(prefs::kAcceptLanguages),
445 requests, values);
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;
474 if (delegate_)
475 delegate_->Closing();
478 void PermissionBubbleViewViews::Toggle(int index, bool value) {
479 if (delegate_)
480 delegate_->ToggleAccept(index, value);
483 void PermissionBubbleViewViews::Accept() {
484 if (delegate_)
485 delegate_->Accept();
488 void PermissionBubbleViewViews::Deny() {
489 if (delegate_)
490 delegate_->Deny();