File: accessibility_handler_browsertest.cc

package info (click to toggle)
chromium 138.0.7204.183-1~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 6,080,960 kB
  • sloc: cpp: 34,937,079; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,954; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,811; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (358 lines) | stat: -rw-r--r-- 13,907 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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// Copyright 2021 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/ui/webui/ash/settings/pages/a11y/accessibility_handler.h"

#include <memory>
#include <optional>
#include <set>
#include <string_view>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "base/containers/adapters.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/input_method/mock_input_method_engine.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "components/language/core/browser/pref_names.h"
#include "components/language/core/common/locale_util.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/test_helper.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_web_ui.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/base/ime/ash/input_method_descriptor.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/input_method_util.h"

using ::testing::Contains;
using ::testing::Not;

namespace ash::settings {

namespace {

// Use a real domain to avoid policy loading problems.
constexpr char kTestUserName[] = "owner@gmail.com";
constexpr char kTestUserHash[] = "1234567890-hash";
constexpr GaiaId::Literal kTestUserGaiaId{"9876543210"};

}  // namespace

class TestAccessibilityHandler : public AccessibilityHandler {
 public:
  explicit TestAccessibilityHandler(Profile* profile)
      : AccessibilityHandler(profile) {}
  ~TestAccessibilityHandler() override = default;
};

class AccessibilityHandlerTest : public InProcessBrowserTest {
 public:
  AccessibilityHandlerTest()
      : mock_ime_engine_handler_(
            std::make_unique<input_method::MockInputMethodEngine>()) {}
  AccessibilityHandlerTest(const AccessibilityHandlerTest&) = delete;
  AccessibilityHandlerTest& operator=(const AccessibilityHandlerTest&) = delete;
  ~AccessibilityHandlerTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    scoped_feature_list_.InitWithFeatures(
        {features::kOnDeviceSpeechRecognition}, {});

    // Use a persisted user.
    command_line->AppendSwitchASCII(ash::switches::kLoginUser, kTestUserName);
    command_line->AppendSwitchASCII(ash::switches::kLoginProfile,
                                    kTestUserHash);
  }

  void SetUpLocalStatePrefService(PrefService* local_state) override {
    InProcessBrowserTest::SetUpLocalStatePrefService(local_state);

    // Register a persisted user.
    user_manager::TestHelper::RegisterPersistedUser(
        *local_state,
        AccountId::FromUserEmailGaiaId(kTestUserName, kTestUserGaiaId));
  }

  void SetUpOnMainThread() override {
    handler_ = std::make_unique<TestAccessibilityHandler>(browser()->profile());
    handler_->set_web_ui(&web_ui_);
    handler_->RegisterMessages();
    handler_->AllowJavascriptForTesting();
    base::RunLoop().RunUntilIdle();

    // Set the Dictation locale for tests.
    SetDictationLocale("en-US");
  }

  void TearDownOnMainThread() override {
    handler_->DisallowJavascript();
    handler_.reset();
  }

  size_t GetNumWebUICalls() { return web_ui_.call_data().size(); }

  void AssertWebUICalls(unsigned int num) {
    ASSERT_EQ(num, web_ui_.call_data().size());
  }

  bool WasWebUIListenerCalledWithStringArgument(
      const std::string& expected_listener,
      const std::string& expected_argument) {
    for (const std::unique_ptr<content::TestWebUI::CallData>& data :
         base::Reversed(web_ui_.call_data())) {
      std::string listener = data->arg1()->GetString();
      if (!data->arg2()->is_string()) {
        // Only look for listeners with a single string argument. Continue
        // silently if we find anything else.
        continue;
      }
      std::string listener_argument = data->arg2()->GetString();

      if (data->function_name() == "cr.webUIListenerCallback" &&
          listener == expected_listener &&
          expected_argument == listener_argument) {
        return true;
      }
    }

    return false;
  }

  bool GetWebUIListenerArgumentListValue(const std::string& expected_listener,
                                         const base::Value::List*& argument) {
    for (const std::unique_ptr<content::TestWebUI::CallData>& data :
         base::Reversed(web_ui_.call_data())) {
      std::string listener;
      if (data->arg1()->is_string()) {
        listener = data->arg1()->GetString();
      }
      if (data->function_name() == "cr.webUIListenerCallback" &&
          listener == expected_listener) {
        if (!data->arg2()->is_list()) {
          return false;
        }
        argument = &data->arg2()->GetList();
        return true;
      }
    }

    return false;
  }

  void MaybeAddDictationLocales() { handler_->MaybeAddDictationLocales(); }

  void SetDictationLocale(const std::string& locale) {
    ProfileManager::GetActiveUserProfile()->GetPrefs()->SetString(
        prefs::kAccessibilityDictationLocale, locale);
  }

  speech::SodaInstaller* soda_installer() {
    return speech::SodaInstaller::GetInstance();
  }

  speech::LanguageCode en_us() { return speech::LanguageCode::kEnUs; }
  speech::LanguageCode fr_fr() { return speech::LanguageCode::kFrFr; }
  content::TestWebUI* web_ui() { return &web_ui_; }

  std::unique_ptr<input_method::MockInputMethodEngine> mock_ime_engine_handler_;

 private:
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<TestAccessibilityHandler> handler_;
  content::TestWebUI web_ui_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Ensures that AccessibilityHandler listens to SODA download state changes, and
// fires the correct listener when SODA AND the language pack matching the
// Dictation locale are installed.
IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest, OnSodaInstalledNotification) {
  SetDictationLocale("fr-FR");
  size_t num_calls = GetNumWebUICalls();
  // Pretend that the SODA binary was installed. We still need to wait for the
  // correct language pack before doing anything.
  soda_installer()->NotifySodaInstalledForTesting();
  AssertWebUICalls(num_calls);
  soda_installer()->NotifySodaInstalledForTesting(en_us());
  AssertWebUICalls(num_calls);
  soda_installer()->NotifySodaInstalledForTesting(fr_fr());
  AssertWebUICalls(num_calls + 1);
  ASSERT_TRUE(WasWebUIListenerCalledWithStringArgument(
      "dictation-locale-menu-subtitle-changed",
      "French (France) is processed locally and works offline"));
}

// Verifies that the correct string is sent to the JavaScript end of the web UI.
// Ensures we only notify the user of progress for the language pack matching
// the Dictation locale.
IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest, OnSodaProgressNotification) {
  size_t num_calls = GetNumWebUICalls();
  soda_installer()->NotifySodaProgressForTesting(50, fr_fr());
  AssertWebUICalls(num_calls);
  soda_installer()->NotifySodaProgressForTesting(50, en_us());
  AssertWebUICalls(num_calls + 1);
  soda_installer()->NotifySodaProgressForTesting(50);
  AssertWebUICalls(num_calls + 2);
  ASSERT_TRUE(WasWebUIListenerCalledWithStringArgument(
      "dictation-locale-menu-subtitle-changed",
      "Downloading speech recognition files… 50%"));
}

// Verifies that the correct string is sent to the JavaScript end of the web UI
// when the SODA binary fails to download.
IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest, OnSodaErrorNotification) {
  size_t num_calls = GetNumWebUICalls();
  soda_installer()->NotifySodaErrorForTesting();
  AssertWebUICalls(num_calls + 1);
  ASSERT_TRUE(WasWebUIListenerCalledWithStringArgument(
      "dictation-locale-menu-subtitle-changed",
      "Couldn’t download English (United States) speech files. Download will "
      "be attempted later. Speech is sent to Google for processing until "
      "download is completed."));
}

// Verifies that the correct listener is fired when the language pack matching
// the Dictation locale fails to download.
IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest,
                       OnSodaLanguageErrorNotification) {
  size_t num_calls = GetNumWebUICalls();
  // Do nothing if the failed language pack is different than the Dictation
  // locale.
  soda_installer()->NotifySodaErrorForTesting(fr_fr());
  AssertWebUICalls(num_calls);
  // Fire the correct listener when the language pack matching the Dictation
  // locale fails.
  soda_installer()->NotifySodaErrorForTesting(en_us());
  AssertWebUICalls(num_calls + 1);
  ASSERT_TRUE(WasWebUIListenerCalledWithStringArgument(
      "dictation-locale-menu-subtitle-changed",
      "Couldn’t download English (United States) speech files. Download will "
      "be attempted later. Speech is sent to Google for processing until "
      "download is completed."));
}

IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest, DictationLocalesCalculation) {
  input_method::InputMethodManager* ime_manager =
      input_method::InputMethodManager::Get();

  struct {
    std::string application_locale;
    std::vector<std::string> ime_locales;
    std::string preferred_languages;
    std::set<std::string_view> expected_recommended_prefixes;
  } kTestCases[] = {
      {"en-US", {}, "", {"en"}},
      {"en", {}, "", {"en"}},
      {"fr", {}, "", {"fr"}},
      {"en", {"en", "en-US"}, "", {"en"}},
      {"en", {"en", "en-US"}, "en", {"en"}},
      {"en", {"en", "es"}, "", {"en", "es"}},
      {"en", {"fr", "es", "fr-FR"}, "", {"en", "es", "fr"}},
      {"it-IT", {"ar", "is-IS", "uk"}, "", {"it", "ar", "is", "uk"}},
      {"en", {"fr", "es", "fr-FR"}, "en-US,it-IT", {"en", "es", "fr", "it"}},
      {"en", {}, "en-US,it-IT,uk", {"en", "it", "uk"}},
  };
  for (const auto& testcase : kTestCases) {
    // Set application locale.
    g_browser_process->SetApplicationLocale(testcase.application_locale);

    // Set up fake IMEs.
    auto state =
        ime_manager->CreateNewState(ProfileManager::GetActiveUserProfile());
    ime_manager->SetState(state);
    input_method::InputMethodDescriptors imes;
    for (auto& locale : testcase.ime_locales) {
      std::string id = "fake-ime-extension-" + locale;
      input_method::InputMethodDescriptor descriptor(
          id, locale, std::string(), std::string(), {locale}, false, GURL(),
          GURL(), /*handwriting_language=*/std::nullopt);
      imes.push_back(descriptor);
    }
    ime_manager->GetInputMethodUtil()->ResetInputMethods(imes);

    for (auto& descriptor : imes) {
      state->AddInputMethodExtension(descriptor.id(), {descriptor},
                                     mock_ime_engine_handler_.get());
      ASSERT_TRUE(state->EnableInputMethod(descriptor.id()));
    }

    // Set up fake preferred languages.
    browser()->profile()->GetPrefs()->SetString(
        language::prefs::kPreferredLanguages, testcase.preferred_languages);

    MaybeAddDictationLocales();

    const base::Value::List* argument = nullptr;
    ASSERT_TRUE(
        GetWebUIListenerArgumentListValue("dictation-locales-set", argument));
    for (const base::Value& it : *argument) {
      const base::Value::Dict& dict = it.GetDict();
      std::string_view language_code =
          language::SplitIntoMainAndTail(*(dict.FindString("value"))).first;
      // Only expect some locales to be recommended based on application and
      // IME languages.
      if (*(dict.FindBool("recommended"))) {
        EXPECT_THAT(testcase.expected_recommended_prefixes,
                    Contains(language_code));
      } else {
        EXPECT_THAT(testcase.expected_recommended_prefixes,
                    Not(Contains(language_code)));
      }
    }
  }
}

IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest,
                       DictationLocalesOfflineAndInstalled) {
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
  MaybeAddDictationLocales();
  const base::Value::List* argument = nullptr;
  ASSERT_TRUE(
      GetWebUIListenerArgumentListValue("dictation-locales-set", argument));

  for (auto& it : *argument) {
    const base::Value::Dict& dict = it.GetDict();
    const std::string locale = *dict.FindString("value");
    bool works_offline = dict.FindBool("worksOffline").value();
    bool installed = dict.FindBool("installed").value();
    if (locale == speech::kUsEnglishLocale) {
      EXPECT_TRUE(works_offline);
      EXPECT_TRUE(installed);
    } else {
      // Some locales other than en-us can be installed offline, but should not
      // be.
      EXPECT_FALSE(installed) << " for locale " << locale;
    }
  }
}

IN_PROC_BROWSER_TEST_F(AccessibilityHandlerTest, GetStartupSoundEnabled) {
  AccessibilityManager::Get()->SetStartupSoundEnabled(true);

  size_t call_data_count_before_call = web_ui()->call_data().size();

  base::Value::List empty_args;
  web_ui()->HandleReceivedMessage("getStartupSoundEnabled", empty_args);

  ASSERT_EQ(call_data_count_before_call + 1u, web_ui()->call_data().size());

  const content::TestWebUI::CallData& call_data =
      *(web_ui()->call_data()[call_data_count_before_call]);
  EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
  EXPECT_EQ("startup-sound-setting-retrieved", call_data.arg1()->GetString());
  EXPECT_TRUE(call_data.arg2()->GetBool());
}

}  // namespace ash::settings