File: device_status_icon_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (344 lines) | stat: -rw-r--r-- 14,777 bytes parent folder | download | duplicates (6)
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
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/device_notifications/device_status_icon_unittest.h"

#include <optional>
#include <string>

#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/status_icons/status_icon.h"
#include "chrome/browser/status_icons/status_icon_menu_model.h"
#include "chrome/browser/status_icons/status_tray.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

namespace {

std::u16string GetExpectedOriginConnectionCountLabel(Profile* profile,
                                                     const url::Origin& origin,
                                                     const std::string& name,
                                                     int connection_count) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
  if (origin.scheme() == extensions::kExtensionScheme) {
    if (connection_count == 0) {
      return base::UTF8ToUTF16(base::StringPrintf(
          "Extension \"%s\" was accessing devices", name.c_str()));
    }
    return base::UTF8ToUTF16(base::StringPrintf(
        "Extension \"%s\" is accessing %d %s", name.c_str(), connection_count,
        (connection_count <= 1 ? "device" : "devices")));
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
  NOTREACHED();
}

class MockStatusIcon : public StatusIcon {
 public:
  explicit MockStatusIcon(const std::u16string& tool_tip)
      : tool_tip_(tool_tip) {}
  void SetImage(const gfx::ImageSkia& image) override {}
  void SetToolTip(const std::u16string& tool_tip) override {
    tool_tip_ = tool_tip;
  }
  void DisplayBalloon(const gfx::ImageSkia& icon,
                      const std::u16string& title,
                      const std::u16string& contents,
                      const message_center::NotifierId& notifier_id) override {}
  void UpdatePlatformContextMenu(StatusIconMenuModel* menu) override {
    menu_item_ = menu;
  }
  const std::u16string& tool_tip() const { return tool_tip_; }
  StatusIconMenuModel* menu_item() const { return menu_item_; }

 private:
  raw_ptr<StatusIconMenuModel> menu_item_ = nullptr;
  std::u16string tool_tip_;
};

class MockStatusTray : public StatusTray {
 public:
  std::unique_ptr<StatusIcon> CreatePlatformStatusIcon(
      StatusIconType type,
      const gfx::ImageSkia& image,
      const std::u16string& tool_tip) override {
    return std::make_unique<MockStatusIcon>(tool_tip);
  }

  const StatusIcons& GetStatusIconsForTest() const { return status_icons(); }
};

}  // namespace

DeviceStatusIconTestBase::DeviceStatusIconTestBase(
    std::u16string about_device_label,
    std::u16string device_content_settings_label)
    : about_device_label_(std::move(about_device_label)),
      device_content_settings_label_(std::move(device_content_settings_label)) {
}

void DeviceStatusIconTestBase::SetUp() {
  DeviceSystemTrayIconTestBase::SetUp();
  TestingBrowserProcess::GetGlobal()->SetStatusTray(
      std::make_unique<MockStatusTray>());
}

void DeviceStatusIconTestBase::TearDown() {
  DeviceSystemTrayIconTestBase::TearDown();
  TestingBrowserProcess::GetGlobal()->SetStatusTray(nullptr);
}

void DeviceStatusIconTestBase::CheckIcon(
    const std::vector<DeviceSystemTrayIconTestBase::ProfileItem>&
        profile_connection_counts) {
  const auto* status_tray = static_cast<MockStatusTray*>(
      TestingBrowserProcess::GetGlobal()->status_tray());
  ASSERT_TRUE(status_tray);
  ASSERT_EQ(status_tray->GetStatusIconsForTest().size(), 1u);
  const auto* status_icon = static_cast<MockStatusIcon*>(
      status_tray->GetStatusIconsForTest().back().icon.get());

  // Sort the |profile_connection_counts| by the address of the profile
  // pointer. This is necessary because the menu items are created by
  // iterating through a structure of flat_map<Profile*, bool>.
  auto sorted_profile_connection_counts = profile_connection_counts;
  std::ranges::sort(sorted_profile_connection_counts);
  size_t total_connection_count = 0;
  size_t total_origin_count = 0;
  auto* menu_item = status_icon->menu_item();
  int menu_idx = 1;
  int expected_command_id = IDC_DEVICE_SYSTEM_TRAY_ICON_FIRST;
  CheckClickableMenuItem(menu_item, menu_idx++, about_device_label_,
                         expected_command_id++, /*click=*/false);
  for (const auto& [profile, origin_items] : sorted_profile_connection_counts) {
    total_origin_count += origin_items.size();
    auto sorted_origin_items = origin_items;
    // Sort the |origin_items| by origin. This is necessary because the origin
    // items for each profile in the menu are created by iterating through a
    // structure of flat_map<url::Origin, ...>.
    std::ranges::sort(sorted_origin_items);
    auto* connection_tracker = GetDeviceConnectionTracker(profile,
                                                          /*create=*/false);
    ASSERT_TRUE(connection_tracker);
    CheckSeparatorMenuItem(menu_item, menu_idx++);
    CheckMenuItemLabel(menu_item, menu_idx++,
                       base::UTF8ToUTF16(profile->GetProfileUserName()));
    EXPECT_CALL(*GetMockDeviceConnectionTracker(connection_tracker),
                ShowContentSettingsExceptions());
    CheckClickableMenuItem(menu_item, menu_idx++,
                           device_content_settings_label_,
                           expected_command_id++, /*click=*/true);
    for (const auto& [origin, connection_count, name] : sorted_origin_items) {
      EXPECT_CALL(*GetMockDeviceConnectionTracker(connection_tracker),
                  ShowSiteSettings(origin));
      CheckClickableMenuItem(menu_item, menu_idx++,
                             GetExpectedOriginConnectionCountLabel(
                                 profile, origin, name, connection_count),
                             expected_command_id++, /*click=*/true);
      total_connection_count += connection_count;
    }
  }
  CheckMenuItemLabel(
      menu_item, 0,
      GetExpectedTitle(total_origin_count,
                       override_title_total_connection_count_.value_or(
                           total_connection_count)));
  EXPECT_EQ(status_icon->tool_tip(),
            GetExpectedTitle(total_origin_count,
                             override_title_total_connection_count_.value_or(
                                 total_connection_count)));
  EXPECT_LE(expected_command_id, IDC_DEVICE_SYSTEM_TRAY_ICON_LAST + 1);
}

void DeviceStatusIconTestBase::CheckIconHidden() {
  const auto* status_tray = static_cast<MockStatusTray*>(
      TestingBrowserProcess::GetGlobal()->status_tray());
  ASSERT_TRUE(status_tray);
  EXPECT_TRUE(status_tray->GetStatusIconsForTest().empty());
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
void DeviceStatusIconTestBase::TestNumCommandIdOverLimitExtensionOrigin() {
  // There are only 40 command ids available. The test creates a scenario that
  // will use more than 40 command ids.

  // Each profile with one origin requires two command IDs (one for "About
  // Device" and one for "extension is connecting to 1 device"). The below for
  // loop sets up 19 profiles, which will consume 39 menu items (1 + 19 * 2).
  size_t num_profiles = 19;
  std::vector<DeviceSystemTrayIconTestBase::ProfileItem>
      profile_connection_counts;
  for (size_t idx = 0; idx < num_profiles; idx++) {
    std::string profile_name = base::StringPrintf("user%zu", idx);
    auto* profile = CreateTestingProfile(profile_name);
    auto extension = CreateExtensionWithName("Test Extension");
    AddExtensionToProfile(profile, extension.get());
    auto* connection_tracker =
        GetDeviceConnectionTracker(profile, /*create=*/true);
    connection_tracker->IncrementConnectionCount(extension->origin());
    profile_connection_counts.push_back(
        {profile, {{extension->origin(), 1, extension->name()}}});
  }
  CheckIcon(profile_connection_counts);

  // Adding one more profile and it will hit the limit.
  {
    std::string profile_name = base::StringPrintf("user%zu", num_profiles);
    auto* profile = CreateTestingProfile(profile_name);
    auto extension = CreateExtensionWithName("Test Extension");
    AddExtensionToProfile(profile, extension.get());
    auto* connection_tracker = GetDeviceConnectionTracker(profile,
                                                          /*create=*/true);
    connection_tracker->IncrementConnectionCount(extension->origin());
    // The origin connection menu item will not be added because the limit of
    // connections has been reached. However, icon items are inserted by
    // iterating over a flat_map<Profile*, bool> structure, so it needs to
    // identify the last profile by sorting profiles and remove its origin
    // count.
    profile_connection_counts.push_back(
        {profile, {{extension->origin(), 1, extension->name()}}});
    std::ranges::sort(profile_connection_counts);
    profile_connection_counts.back().second.clear();
    // The total connection count in the title still captures all of the origins
    override_title_total_connection_count_ = 20;
    CheckIcon(profile_connection_counts);
  }
}

void DeviceStatusIconTestBase::TestProfileUserNameExtensionOrigin() {
  std::vector<DeviceSystemTrayIconTestBase::ProfileItem>
      profile_connection_counts;
  // std::get<1>(profiles[i]) is the old profile name.
  // std::get<2>(profiles[i]) is the new profile name.
  std::vector<std::tuple<Profile*, std::string, std::string>> profiles;
  for (size_t idx = 0; idx < 2; idx++) {
    std::string profile_name = base::StringPrintf("user%zu", idx);
    std::string new_profile_name = base::StringPrintf("user%zu-newname", idx);
    auto* profile = CreateTestingProfile(profile_name);
    auto extension = CreateExtensionWithName("Test Extension");
    AddExtensionToProfile(profile, extension.get());
    auto* connection_tracker = GetDeviceConnectionTracker(profile,
                                                          /*create=*/true);
    connection_tracker->IncrementConnectionCount(extension->origin());
    profile_connection_counts.push_back(
        {profile, {{extension->origin(), 1, extension->name()}}});
    profiles.emplace_back(profile, profile_name, new_profile_name);
  }
  CheckIcon(profile_connection_counts);

  const auto* status_tray = static_cast<MockStatusTray*>(
      TestingBrowserProcess::GetGlobal()->status_tray());
  ASSERT_TRUE(status_tray);
  ASSERT_EQ(status_tray->GetStatusIconsForTest().size(), 1u);
  const auto* status_icon = static_cast<MockStatusIcon*>(
      status_tray->GetStatusIconsForTest().back().icon.get());

  // Sort the |profiles| by the address of the profile pointer. This is
  // necessary because the menu items are created by iterating through a
  // structure of flat_map<Profile*, bool>.
  std::ranges::sort(profiles);

  // The below is status icon items layout, profile1 name is on [3] and profile2
  // name is on [7].
  // ---------------------------------------------------
  // [0]|Google Chrome is accessing Device device(s)   |
  // [1]|About Device device                           |
  // [2]|---------------Separator----------------------|
  // [3]|Profile1 name                                 |
  // [4]|Device Content Setting for Profile1           |
  // [5]|Extension name                                |
  // [6]|---------------Separator----------------------|
  // [7]|Profile2 name                                 |
  // [8]|Device Content Setting for Profile2           |
  // [9]|Extension name                                |
  // ---------------------------------------------------

  int profile_position1 = 3;
  int profile_position2 = 7;

  // Check the current profile names.
  {
    auto* menu_item = status_icon->menu_item();
    CheckMenuItemLabel(
        menu_item, profile_position1,
        base::UTF8ToUTF16(std::get<0>(profiles[0])->GetProfileUserName()));
    CheckMenuItemLabel(
        menu_item, profile_position2,
        base::UTF8ToUTF16(std::get<0>(profiles[1])->GetProfileUserName()));
  }

  // Change the first profile name.
  {
    profile_manager()
        ->profile_attributes_storage()
        ->GetProfileAttributesWithPath(std::get<0>(profiles[0])->GetPath())
        ->SetLocalProfileName(base::UTF8ToUTF16(std::get<2>(profiles[0])),
                              /*is_default_name*/ false);

    auto* menu_item = status_icon->menu_item();
    CheckMenuItemLabel(menu_item, profile_position1,
                       base::UTF8ToUTF16(std::get<2>(profiles[0])));
    CheckMenuItemLabel(menu_item, profile_position2,
                       base::UTF8ToUTF16(std::get<1>(profiles[1])));
  }

  // Change the second profile name.
  {
    profile_manager()
        ->profile_attributes_storage()
        ->GetProfileAttributesWithPath(std::get<0>(profiles[1])->GetPath())
        ->SetLocalProfileName(base::UTF8ToUTF16(std::get<2>(profiles[1])),
                              /*is_default_name*/ false);

    auto* menu_item = status_icon->menu_item();
    CheckMenuItemLabel(menu_item, profile_position1,
                       base::UTF8ToUTF16(std::get<2>(profiles[0])));
    CheckMenuItemLabel(menu_item, profile_position2,
                       base::UTF8ToUTF16(std::get<2>(profiles[1])));
  }
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

void DeviceStatusIconTestBase::CheckSeparatorMenuItem(
    StatusIconMenuModel* menu_item,
    size_t menu_idx) {
  ASSERT_LT(menu_idx, menu_item->GetItemCount());
  EXPECT_EQ(menu_item->GetSeparatorTypeAt(menu_idx), ui::NORMAL_SEPARATOR);
}

void DeviceStatusIconTestBase::CheckMenuItemLabel(
    StatusIconMenuModel* menu_item,
    size_t menu_idx,
    std::u16string label) {
  ASSERT_LT(menu_idx, menu_item->GetItemCount());
  EXPECT_EQ(menu_item->GetLabelAt(menu_idx), label);
}

void DeviceStatusIconTestBase::CheckClickableMenuItem(
    StatusIconMenuModel* menu_item,
    size_t menu_idx,
    std::u16string label,
    int command_id,
    bool click) {
  CheckMenuItemLabel(menu_item, menu_idx, label);
  ASSERT_LT(menu_idx, menu_item->GetItemCount());
  EXPECT_EQ(menu_item->GetCommandIdAt(menu_idx), command_id);
  if (click) {
    menu_item->ActivatedAt(menu_idx);
  }
}