File: permission_selector_row.cc

package info (click to toggle)
chromium-browser 70.0.3538.110-1~deb9u1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 1,619,476 kB
  • sloc: cpp: 13,024,755; ansic: 1,349,823; python: 916,672; xml: 314,489; java: 280,047; asm: 276,936; perl: 75,771; objc: 66,634; sh: 45,860; cs: 28,354; php: 11,064; makefile: 10,911; yacc: 9,109; tcl: 8,403; ruby: 4,065; lex: 1,779; pascal: 1,411; lisp: 1,055; awk: 41; jsp: 39; sed: 17; sql: 3
file content (428 lines) | stat: -rw-r--r-- 16,727 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
// 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/page_info/permission_selector_row.h"

#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/page_info/page_info_ui.h"
#include "chrome/browser/ui/page_info/permission_menu_model.h"
#include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "components/strings/grit/components_strings.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/combobox_model.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"

namespace {

// The text context / style of the |PermissionSelectorRow| combobox and label.
constexpr int kPermissionRowTextContext = views::style::CONTEXT_LABEL;
constexpr int kPermissionRowTextStyle = views::style::STYLE_PRIMARY;

// Calculates the amount of padding to add beneath a |PermissionSelectorRow|
// depending on whether it has an accompanying permission decision reason.
int CalculatePaddingBeneathPermissionRow(bool has_reason) {
  const int list_item_padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
                                    DISTANCE_CONTROL_LIST_VERTICAL) /
                                2;
  if (!has_reason)
    return list_item_padding;

  const int combobox_height =
      PermissionSelectorRow::MinHeightForPermissionRow();
  // Match the amount of padding above the |PermissionSelectorRow| title text
  // here by calculating its full height of this |PermissionSelectorRow| and
  // subtracting the line height, then dividing everything by two. Note it is
  // assumed the combobox is the tallest part of the row.
  return (list_item_padding * 2 + combobox_height -
          views::style::GetLineHeight(kPermissionRowTextContext,
                                      kPermissionRowTextStyle)) /
         2;
}

}  // namespace

namespace internal {

// The |PermissionMenuButton| provides a menu for selecting a setting a
// permissions type.
class PermissionMenuButton : public views::MenuButton,
                             public views::MenuButtonListener {
 public:
  // Creates a new |PermissionMenuButton| with the passed |text|. The ownership
  // of the |model| remains with the caller and is not transfered to the
  // |PermissionMenuButton|. If the |show_menu_marker| flag is true, then a
  // small icon is be displayed next to the button |text|, indicating that the
  // button opens a drop down menu.
  PermissionMenuButton(const base::string16& text,
                       PermissionMenuModel* model,
                       bool show_menu_marker);
  ~PermissionMenuButton() override;

  // Overridden from views::View.
  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
  void OnNativeThemeChanged(const ui::NativeTheme* theme) override;

 private:
  // Overridden from views::MenuButtonListener.
  void OnMenuButtonClicked(views::MenuButton* source,
                           const gfx::Point& point,
                           const ui::Event* event) override;

  PermissionMenuModel* menu_model_;  // Owned by |PermissionSelectorRow|.
  std::unique_ptr<views::MenuRunner> menu_runner_;

  bool is_rtl_display_;

  DISALLOW_COPY_AND_ASSIGN(PermissionMenuButton);
};

///////////////////////////////////////////////////////////////////////////////
// PermissionMenuButton
///////////////////////////////////////////////////////////////////////////////

PermissionMenuButton::PermissionMenuButton(const base::string16& text,
                                           PermissionMenuModel* model,
                                           bool show_menu_marker)
    : MenuButton(text, this, show_menu_marker), menu_model_(model) {
  // Since PermissionMenuButtons are added to a GridLayout, they are not always
  // sized to their preferred size. Disclosure arrows are always right-aligned,
  // so if the text is not right-aligned, awkward space appears between the text
  // and the arrow.
  SetHorizontalAlignment(gfx::ALIGN_RIGHT);

  // Update the themed border before the NativeTheme is applied. Usually this
  // happens in a call to LabelButton::OnNativeThemeChanged(). However, if
  // PermissionMenuButton called that from its override, the NativeTheme would
  // be available, and the button would get native GTK styling on Linux.
  UpdateThemedBorder();

  SetFocusForPlatform();
  set_request_focus_on_press(true);
  is_rtl_display_ =
      base::i18n::RIGHT_TO_LEFT == base::i18n::GetStringDirection(text);
}

PermissionMenuButton::~PermissionMenuButton() {}

void PermissionMenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  MenuButton::GetAccessibleNodeData(node_data);
  node_data->SetValue(GetText());
}

void PermissionMenuButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
  SetTextColor(
      views::Button::STATE_NORMAL,
      theme->GetSystemColor(ui::NativeTheme::kColorId_LabelEnabledColor));
  SetTextColor(
      views::Button::STATE_HOVERED,
      theme->GetSystemColor(ui::NativeTheme::kColorId_LabelEnabledColor));
  SetTextColor(
      views::Button::STATE_DISABLED,
      theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor));
}

void PermissionMenuButton::OnMenuButtonClicked(views::MenuButton* source,
                                               const gfx::Point& point,
                                               const ui::Event* event) {
  menu_runner_.reset(
      new views::MenuRunner(menu_model_, views::MenuRunner::HAS_MNEMONICS));

  gfx::Point p(point);
  p.Offset(is_rtl_display_ ? source->width() : -source->width(), 0);
  menu_runner_->RunMenuAt(source->GetWidget()->GetTopLevelWidget(), this,
                          gfx::Rect(p, gfx::Size()), views::MENU_ANCHOR_TOPLEFT,
                          ui::MENU_SOURCE_NONE);
}

// This class adapts a |PermissionMenuModel| into a |ui::ComboboxModel| so that
// |PermissionCombobox| can use it.
class ComboboxModelAdapter : public ui::ComboboxModel {
 public:
  explicit ComboboxModelAdapter(PermissionMenuModel* model) : model_(model) {}
  ~ComboboxModelAdapter() override {}

  void OnPerformAction(int index);

  // Returns the checked index of the underlying PermissionMenuModel, of which
  // there must be exactly one. This is used to choose which index is selected
  // in the PermissionCombobox.
  int GetCheckedIndex();

  // ui::ComboboxModel:
  int GetItemCount() const override;
  base::string16 GetItemAt(int index) override;

 private:
  PermissionMenuModel* model_;
};

void ComboboxModelAdapter::OnPerformAction(int index) {
  int command_id = model_->GetCommandIdAt(index);
  model_->ExecuteCommand(command_id, 0);
}

int ComboboxModelAdapter::GetCheckedIndex() {
  int checked_index = -1;
  for (int i = 0; i < model_->GetItemCount(); ++i) {
    int command_id = model_->GetCommandIdAt(i);
    if (model_->IsCommandIdChecked(command_id)) {
      // This function keeps track of |checked_index| instead of returning early
      // here so that it can DCHECK that there's exactly one selected item,
      // which is not normally guaranteed by MenuModel, but *is* true of
      // PermissionMenuModel.
      DCHECK_EQ(checked_index, -1);
      checked_index = i;
    }
  }
  return checked_index;
}

int ComboboxModelAdapter::GetItemCount() const {
  DCHECK(model_);
  return model_->GetItemCount();
}

base::string16 ComboboxModelAdapter::GetItemAt(int index) {
  return model_->GetLabelAt(index);
}

// The |PermissionCombobox| provides a combobox for selecting a permission type.
// This is only used on platforms where the permission dialog uses a combobox
// instead of a MenuButton (currently, Mac).
class PermissionCombobox : public views::Combobox,
                           public views::ComboboxListener {
 public:
  PermissionCombobox(ComboboxModelAdapter* model,
                     bool enabled,
                     bool use_default);
  ~PermissionCombobox() override;

  void UpdateSelectedIndex(bool use_default);

  void set_min_width(int width) { min_width_ = width; }

  // views::Combobox:
  gfx::Size CalculatePreferredSize() const override;

 private:
  // views::ComboboxListener:
  void OnPerformAction(Combobox* combobox) override;

  ComboboxModelAdapter* model_;

  // Minimum width for |PermissionCombobox|.
  int min_width_ = 0;

  DISALLOW_COPY_AND_ASSIGN(PermissionCombobox);
};

PermissionCombobox::PermissionCombobox(ComboboxModelAdapter* model,
                                       bool enabled,
                                       bool use_default)
    : views::Combobox(model), model_(model) {
  set_listener(this);
  SetEnabled(enabled);
  UpdateSelectedIndex(use_default);
  set_size_to_largest_label(false);
  ModelChanged();
}

PermissionCombobox::~PermissionCombobox() {}

void PermissionCombobox::UpdateSelectedIndex(bool use_default) {
  int index = model_->GetCheckedIndex();
  if (use_default && index == -1) {
    index = 0;
  }
  SetSelectedIndex(index);
}

gfx::Size PermissionCombobox::CalculatePreferredSize() const {
  gfx::Size preferred_size = Combobox::CalculatePreferredSize();
  preferred_size.SetToMax(gfx::Size(min_width_, 0));
  return preferred_size;
}

void PermissionCombobox::OnPerformAction(Combobox* combobox) {
  model_->OnPerformAction(combobox->selected_index());
}

}  // namespace internal

///////////////////////////////////////////////////////////////////////////////
// PermissionSelectorRow
///////////////////////////////////////////////////////////////////////////////

PermissionSelectorRow::PermissionSelectorRow(
    Profile* profile,
    const GURL& url,
    const PageInfoUI::PermissionInfo& permission,
    views::GridLayout* layout)
    : profile_(profile),
      icon_(nullptr),
      menu_button_(nullptr),
      combobox_(nullptr) {
  const int list_item_padding = ChromeLayoutProvider::Get()->GetDistanceMetric(
                                    DISTANCE_CONTROL_LIST_VERTICAL) /
                                2;
  layout->StartRowWithPadding(1.0, PageInfoBubbleView::kPermissionColumnSetId,
                              views::GridLayout::kFixedSize, list_item_padding);

  // Create the permission icon and label.
  icon_ = new NonAccessibleImageView();
  layout->AddView(icon_);
  // Create the label that displays the permission type.
  label_ =
      new views::Label(PageInfoUI::PermissionTypeToUIString(permission.type),
                       CONTEXT_BODY_TEXT_LARGE);
  icon_->SetImage(
      PageInfoUI::GetPermissionIcon(permission, label_->enabled_color()));
  layout->AddView(label_);
  // Create the menu model.
  menu_model_.reset(new PermissionMenuModel(
      profile, url, permission,
      base::Bind(&PermissionSelectorRow::PermissionChanged,
                 base::Unretained(this))));

  // Create the permission menu button.
  InitializeComboboxView(layout, permission);

  // Show the permission decision reason, if it was not the user.
  base::string16 reason =
      PageInfoUI::PermissionDecisionReasonToUIString(profile, permission, url);
  if (!reason.empty()) {
    layout->StartRow(1.0, PageInfoBubbleView::kPermissionColumnSetId);
    layout->SkipColumns(1);
    views::Label* secondary_label = new views::Label(reason);
    secondary_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    secondary_label->SetEnabledColor(PageInfoUI::GetSecondaryTextColor());
    // The |secondary_label| should wrap when it's too long instead of
    // stretching its parent view horizontally, but also ensure long strings
    // aren't wrapped too early.
    int preferred_width = secondary_label->GetPreferredSize().width();
    secondary_label->SetMultiLine(true);

    views::ColumnSet* column_set =
        layout->GetColumnSet(PageInfoBubbleView::kPermissionColumnSetId);
    DCHECK(column_set);
    // Secondary labels in Harmony may not overlap into space shared with the
    // combobox column.
    const int column_span = 1;

    // Long labels that cannot fit in the existing space under the permission
    // label should be allowed to use up to |kMaxSecondaryLabelWidth| for
    // display.
    constexpr int kMaxSecondaryLabelWidth = 140;
    if (preferred_width > kMaxSecondaryLabelWidth) {
      layout->AddView(secondary_label, column_span, 1.0,
                      views::GridLayout::LEADING, views::GridLayout::CENTER,
                      kMaxSecondaryLabelWidth, 0);
    } else {
      layout->AddView(secondary_label, column_span, 1.0,
                      views::GridLayout::FILL, views::GridLayout::CENTER);
    }
  }
  layout->AddPaddingRow(views::GridLayout::kFixedSize,
                        CalculatePaddingBeneathPermissionRow(!reason.empty()));
}

PermissionSelectorRow::~PermissionSelectorRow() {
  // Gross. On paper the Combobox and the ComboboxModelAdapter are both owned by
  // this class, but actually, the Combobox is owned by View and will be
  // destroyed in ~View(), which runs *after* ~PermissionSelectorRow() is done,
  // which means the Combobox gets destroyed after its ComboboxModel, which
  // causes an explosion when the Combobox attempts to stop observing the
  // ComboboxModel. This hack ensures the Combobox is deleted before its
  // ComboboxModel.
  //
  // Technically, the MenuButton has the same problem, but MenuButton doesn't
  // use its model in its destructor.
  delete combobox_;
}

// static
int PermissionSelectorRow::MinHeightForPermissionRow() {
  return ChromeLayoutProvider::Get()->GetControlHeightForFont(
      kPermissionRowTextContext, kPermissionRowTextStyle,
      views::Combobox::GetFontList());
}

void PermissionSelectorRow::AddObserver(
    PermissionSelectorRowObserver* observer) {
  observer_list_.AddObserver(observer);
}

void PermissionSelectorRow::InitializeComboboxView(
    views::GridLayout* layout,
    const PageInfoUI::PermissionInfo& permission) {
  bool button_enabled =
      permission.source == content_settings::SETTING_SOURCE_USER;
  combobox_model_adapter_.reset(
      new internal::ComboboxModelAdapter(menu_model_.get()));
  combobox_ = new internal::PermissionCombobox(combobox_model_adapter_.get(),
                                               button_enabled, true);
  combobox_->SetEnabled(button_enabled);
  combobox_->SetTooltipText(l10n_util::GetStringFUTF16(
      IDS_PAGE_INFO_SELECTOR_TOOLTIP,
      PageInfoUI::PermissionTypeToUIString(permission.type)));
  layout->AddView(combobox_);
}

void PermissionSelectorRow::PermissionChanged(
    const PageInfoUI::PermissionInfo& permission) {
  // Change the permission icon to reflect the selected setting.
  icon_->SetImage(
      PageInfoUI::GetPermissionIcon(permission, label_->enabled_color()));

  // Update the menu button text to reflect the new setting.
  if (menu_button_) {
    // Re-layout will be done at the |PageInfoBubbleView| level, since
    // that view may need to resize itself to accomodate the new sizes of its
    // contents.
    menu_button_->SetText(PageInfoUI::PermissionActionToUIString(
        profile_, permission.type, permission.setting,
        permission.default_setting, content_settings::SETTING_SOURCE_USER));
  } else if (combobox_) {
    bool use_default = permission.setting == CONTENT_SETTING_DEFAULT;
    combobox_->UpdateSelectedIndex(use_default);
  }

  for (PermissionSelectorRowObserver& observer : observer_list_) {
    observer.OnPermissionChanged(permission);
  }
}

int PermissionSelectorRow::GetComboboxWidth() const {
  DCHECK(combobox_);
  return combobox_->Combobox::GetPreferredSize().width();
}

void PermissionSelectorRow::SetMinComboboxWidth(int width) {
  DCHECK(combobox_);
  combobox_->set_min_width(width);
}

views::View* PermissionSelectorRow::button() {
  // These casts are required because the two arms of a ?: cannot have different
  // types T1 and T2, even if the resulting value of the ?: is about to be a T
  // and T1 and T2 are both subtypes of T.
  return menu_button_ ? static_cast<views::View*>(menu_button_)
                      : static_cast<views::View*>(combobox_);
}