| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "ash/constants/ash_pref_names.h" |
| #include "base/json/json_writer.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/accessibility/accessibility_test_utils.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/test/result_catcher.h" |
| |
| // API tests for chrome.accessibilityFeatures API. |
| // Note that the API is implemented using preference API infrastructure. |
| // See preference_api.cc for the list of accessibility features exposed by the |
| // API and the related preferences. |
| |
| namespace extensions { |
| |
| namespace { |
| |
| // Keys for data in the test config argument that will be set for the test app |
| // to use. |
| // The test that the app should run. |
| const char kTestNameKey[] = "testName"; |
| // Key for list of features enabled when the test is initialized. |
| const char kEnabledFeaturesKey[] = "enabled"; |
| // Key for list of features disabled when the test is initialized. |
| const char kDisabledFeaturesKey[] = "disabled"; |
| |
| // A test extension path. The extension has only |accessibilityFeatures.read| |
| // permission. |
| const char kTestExtensionPathReadPermission[] = |
| "accessibility_features/read_permission/"; |
| |
| // A test extension path. The extension has only |accessibilityFeatures.read| |
| // permission and has manifest v3. |
| const char kTestExtensionPathReadPermissionV3[] = |
| "accessibility_features/mv3/read_permission/"; |
| |
| // A test extension path. The extension has only |accessibilityFeatures.modify| |
| // permission. |
| const char kTestExtensionPathModifyPermission[] = |
| "accessibility_features/modify_permission/"; |
| |
| // A test extension path. The extension has only |accessibilityFeatures.modify| |
| // permission and has manifest v3. |
| const char kTestExtensionPathModifyPermissionV3[] = |
| "accessibility_features/mv3/modify_permission/"; |
| |
| using ManifestVersion = ash::ManifestVersion; |
| |
| enum class Permission { kWriteOnly, kReadOnly }; |
| |
| // A class used to define the parameters of a test case. |
| struct TestConfig { |
| Permission permission; |
| ManifestVersion version; |
| }; |
| |
| // Accessibility features API test. |
| // Tests are parameterized by the permission (write-only or read-only), as well |
| // as the manifest version (v2 or v3). |
| class AccessibilityFeaturesApiTest |
| : public ExtensionApiTest, |
| public testing::WithParamInterface<TestConfig> { |
| public: |
| AccessibilityFeaturesApiTest() = default; |
| virtual ~AccessibilityFeaturesApiTest() = default; |
| |
| protected: |
| // Returns pref service to be used to initialize and later verify |
| // accessibility preference values. |
| PrefService* GetPrefs() { return browser()->profile()->GetPrefs(); } |
| |
| // Returns the path of the extension that should be used in a parameterized |
| // test. |
| const char* GetTestExtensionPath() const { |
| Permission permission = GetParam().permission; |
| ManifestVersion version = GetParam().version; |
| if (version == ManifestVersion::kTwo && |
| permission == Permission::kWriteOnly) { |
| return kTestExtensionPathModifyPermission; |
| } else if (version == ManifestVersion::kTwo && |
| permission == Permission::kReadOnly) { |
| return kTestExtensionPathReadPermission; |
| } else if (version == ManifestVersion::kThree && |
| permission == Permission::kWriteOnly) { |
| return kTestExtensionPathModifyPermissionV3; |
| } else if (version == ManifestVersion::kThree && |
| permission == Permission::kReadOnly) { |
| return kTestExtensionPathReadPermissionV3; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| // Whether a parameterized test should have been able to modify accessibility |
| // preferences (i.e. whether the test extension had modify permission). |
| bool ShouldModifyingFeatureSucceed() const { |
| return GetParam().permission == Permission::kWriteOnly; |
| } |
| |
| // Returns preference path for accessibility features as defined by the API. |
| const char* GetPrefForFeature(const std::string& feature) { |
| if (feature == "spokenFeedback") |
| return ash::prefs::kAccessibilitySpokenFeedbackEnabled; |
| if (feature == "largeCursor") |
| return ash::prefs::kAccessibilityLargeCursorEnabled; |
| if (feature == "stickyKeys") |
| return ash::prefs::kAccessibilityStickyKeysEnabled; |
| if (feature == "highContrast") |
| return ash::prefs::kAccessibilityHighContrastEnabled; |
| if (feature == "screenMagnifier") |
| return ash::prefs::kAccessibilityScreenMagnifierEnabled; |
| if (feature == "autoclick") |
| return ash::prefs::kAccessibilityAutoclickEnabled; |
| if (feature == "virtualKeyboard") |
| return ash::prefs::kAccessibilityVirtualKeyboardEnabled; |
| if (feature == "caretHighlight") |
| return ash::prefs::kAccessibilityCaretHighlightEnabled; |
| if (feature == "cursorHighlight") |
| return ash::prefs::kAccessibilityCursorHighlightEnabled; |
| if (feature == "focusHighlight") |
| return ash::prefs::kAccessibilityFocusHighlightEnabled; |
| if (feature == "selectToSpeak") |
| return ash::prefs::kAccessibilitySelectToSpeakEnabled; |
| if (feature == "switchAccess") |
| return ash::prefs::kAccessibilitySwitchAccessEnabled; |
| if (feature == "cursorColor") |
| return ash::prefs::kAccessibilityCursorColorEnabled; |
| if (feature == "dockedMagnifier") |
| return ash::prefs::kDockedMagnifierEnabled; |
| if (feature == "dictation") |
| return ash::prefs::kAccessibilityDictationEnabled; |
| return nullptr; |
| } |
| |
| // Initializes preferences before running the test extension. |
| // |prefs| Pref service which should be initialized. |
| // |enabled_features| List of boolean preference whose value should be set to |
| // true. |
| // |disabled_features| List of boolean preferences whose value should be set |
| // to false. |
| bool InitPrefServiceForTest( |
| PrefService* prefs, |
| const std::vector<std::string>& enabled_features, |
| const std::vector<std::string>& disabled_features) { |
| for (const auto& feature : enabled_features) { |
| const char* const pref_name = GetPrefForFeature(feature); |
| EXPECT_TRUE(pref_name) << "Invalid feature " << feature; |
| if (!pref_name) |
| return false; |
| prefs->SetBoolean(pref_name, true); |
| } |
| |
| for (const auto& feature : disabled_features) { |
| const char* const pref_name = GetPrefForFeature(feature); |
| EXPECT_TRUE(pref_name) << "Invalid feature " << feature; |
| if (!pref_name) |
| return false; |
| prefs->SetBoolean(pref_name, false); |
| } |
| return true; |
| } |
| |
| // Verifies that preferences have the expected value. |
| // |prefs| The pref service to be verified. |
| // |enabled_features| The list of boolean preferences whose value should be |
| // true. |
| // |disabled_features| The list of boolean preferences whose value should be |
| // false. |
| void VerifyPrefServiceState( |
| PrefService* prefs, |
| const std::vector<std::string>& enabled_features, |
| const std::vector<std::string>& disabled_features) { |
| for (const auto& feature : enabled_features) { |
| const char* const pref_name = GetPrefForFeature(feature); |
| ASSERT_TRUE(pref_name) << "Invalid feature " << feature; |
| ASSERT_TRUE(prefs->GetBoolean(pref_name)); |
| } |
| |
| for (const auto& feature : disabled_features) { |
| const char* const pref_name = GetPrefForFeature(feature); |
| ASSERT_TRUE(pref_name) << "Invalid feature " << feature; |
| ASSERT_FALSE(prefs->GetBoolean(pref_name)); |
| } |
| } |
| |
| // Given the test name and list of enabled and disabled features, generates |
| // and sets the JSON string that should be given to the test extension as |
| // test configuration. |
| // The result is saved to |result|. The return value is whether the test |
| // argument was successfully generated. |
| bool GenerateTestArg(const std::string& test_name, |
| const std::vector<std::string>& enabled_features, |
| const std::vector<std::string>& disabled_features, |
| std::string* result) { |
| base::Value::Dict test_arg; |
| test_arg.Set(kTestNameKey, test_name); |
| |
| base::Value::List enabled_list; |
| for (const auto& feature : enabled_features) |
| enabled_list.Append(feature); |
| test_arg.Set(kEnabledFeaturesKey, std::move(enabled_list)); |
| |
| base::Value::List disabled_list; |
| for (const auto& feature : disabled_features) |
| disabled_list.Append(feature); |
| test_arg.Set(kDisabledFeaturesKey, std::move(disabled_list)); |
| |
| return base::JSONWriter::Write(test_arg, result); |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(AccessibilityFeaturesApiTestWritePermission, |
| AccessibilityFeaturesApiTest, |
| ::testing::Values(TestConfig{Permission::kWriteOnly, |
| ManifestVersion::kTwo})); |
| |
| INSTANTIATE_TEST_SUITE_P(AccessibilityFeaturesApiTestReadPermission, |
| AccessibilityFeaturesApiTest, |
| ::testing::Values(TestConfig{Permission::kReadOnly, |
| ManifestVersion::kTwo})); |
| |
| INSTANTIATE_TEST_SUITE_P(AccessibilityFeaturesApiTestWritePermissionV3, |
| AccessibilityFeaturesApiTest, |
| ::testing::Values(TestConfig{ |
| Permission::kWriteOnly, ManifestVersion::kThree})); |
| |
| INSTANTIATE_TEST_SUITE_P(AccessibilityFeaturesApiTestReadPermissionV3, |
| AccessibilityFeaturesApiTest, |
| ::testing::Values(TestConfig{ |
| Permission::kReadOnly, ManifestVersion::kThree})); |
| |
| // Tests that an extension with read permission can read accessibility features |
| // state, while an extension that doesn't have the permission cannot. |
| IN_PROC_BROWSER_TEST_P(AccessibilityFeaturesApiTest, Get) { |
| // WARNING: Make sure that features which load Chrome extension are not among |
| // enabled_features (see |Set| test for the reason). |
| std::vector<std::string> enabled_features = { |
| "cursorColor", "cursorHighlight", "highContrast", |
| "largeCursor", "stickyKeys", |
| }; |
| |
| std::vector<std::string> disabled_features = { |
| "autoclick", |
| "caretHighlight", |
| "dockedMagnifier", |
| "focusHighlight", |
| "screenMagnifier", |
| "selectToSpeak", |
| "spokenFeedback", |
| "switchAccess", |
| "virtualKeyboard", |
| }; |
| |
| ASSERT_TRUE( |
| InitPrefServiceForTest(GetPrefs(), enabled_features, disabled_features)); |
| |
| std::string test_arg; |
| ASSERT_TRUE(GenerateTestArg("getterTest", enabled_features, disabled_features, |
| &test_arg)); |
| |
| bool is_mv2 = GetParam().version == ManifestVersion::kTwo; |
| EXPECT_TRUE(RunExtensionTest( |
| GetTestExtensionPath(), |
| {.custom_arg = test_arg.c_str(), .launch_as_platform_app = is_mv2})) |
| << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_P(AccessibilityFeaturesApiTest, PRE_Get_ComponentApp) { |
| bool is_mv2 = GetParam().version == ManifestVersion::kTwo; |
| EXPECT_FALSE( |
| RunExtensionTest(GetTestExtensionPath(), |
| {.custom_arg = "{}", .launch_as_platform_app = is_mv2}, |
| {.load_as_component = is_mv2})) |
| << message_; |
| } |
| |
| // A regression test for https://crbug.com/454513. Ensure that loading a |
| // component extension with the same version as has previously loaded, correctly |
| // sets up access to accessibility prefs. Otherwise,this is the same as the |
| // |Get| test. |
| IN_PROC_BROWSER_TEST_P(AccessibilityFeaturesApiTest, Get_ComponentApp) { |
| // WARNING: Make sure that features which load Chrome extension are not among |
| // enabled_features (see |Set| test for the reason). |
| std::vector<std::string> enabled_features = { |
| "cursorHighlight", |
| "dockedMagnifier", |
| "highContrast", |
| "largeCursor", |
| "stickyKeys", |
| }; |
| |
| std::vector<std::string> disabled_features = { |
| "autoclick", "caretHighlight", "cursorColor", |
| "focusHighlight", "screenMagnifier", "selectToSpeak", |
| "spokenFeedback", "switchAccess", "virtualKeyboard", |
| }; |
| |
| ASSERT_TRUE( |
| InitPrefServiceForTest(GetPrefs(), enabled_features, disabled_features)); |
| |
| std::string test_arg; |
| ASSERT_TRUE(GenerateTestArg("getterTest", enabled_features, disabled_features, |
| &test_arg)); |
| |
| bool is_mv2 = GetParam().version == ManifestVersion::kTwo; |
| EXPECT_TRUE(RunExtensionTest( |
| GetTestExtensionPath(), |
| {.custom_arg = test_arg.c_str(), .launch_as_platform_app = is_mv2}, |
| {.load_as_component = is_mv2})) |
| << message_; |
| } |
| |
| // Tests that an extension with modify permission can modify accessibility |
| // features, while an extension that doesn't have the permission can't. |
| IN_PROC_BROWSER_TEST_P(AccessibilityFeaturesApiTest, Set) { |
| // WARNING: Make sure that features which load Chrome extension are not |
| // enabled at this point (before the test app is loaded), as that may break |
| // the test: |
| // |RunPlatformAppTestWithArg| waits for the test extension to load by |
| // waiting for EXTENSION_LOADED notification to be observed. It also assumes |
| // that there is only one extension being loaded during this time (it finishes |
| // when the first notification is seen). Enabling spoken feedback, select to |
| // speak, autoclick, or switch access here would break this assumption as it |
| // would induce loading of Chrome extension. |
| std::vector<std::string> enabled_features = { |
| "caretHighlight", |
| "cursorColor", |
| "focusHighlight", |
| "stickyKeys", |
| }; |
| |
| std::vector<std::string> disabled_features = { |
| "autoclick", |
| "cursorHighlight", |
| "dockedMagnifier", |
| "highContrast", |
| "largeCursor", |
| "screenMagnifier", |
| "selectToSpeak", |
| "spokenFeedback", |
| "switchAccess", |
| "virtualKeyboard", |
| }; |
| |
| ASSERT_TRUE( |
| InitPrefServiceForTest(GetPrefs(), enabled_features, disabled_features)); |
| |
| std::string test_arg; |
| ASSERT_TRUE(GenerateTestArg("setterTest", enabled_features, disabled_features, |
| &test_arg)); |
| bool is_mv2 = GetParam().version == ManifestVersion::kTwo; |
| // The test extension attempts to flip all feature values. |
| ASSERT_TRUE(RunExtensionTest( |
| GetTestExtensionPath(), |
| {.custom_arg = test_arg.c_str(), .launch_as_platform_app = is_mv2})) |
| << message_; |
| |
| // The test tries to flip the feature states. |
| if (ShouldModifyingFeatureSucceed()) { |
| VerifyPrefServiceState(GetPrefs(), disabled_features, enabled_features); |
| } else { |
| VerifyPrefServiceState(GetPrefs(), enabled_features, disabled_features); |
| } |
| } |
| |
| // Tests that an extension with read permission is notified when accessibility |
| // features change. |
| IN_PROC_BROWSER_TEST_P(AccessibilityFeaturesApiTest, ObserveFeatures) { |
| // WARNING: Make sure that features which load Chrome extension are not among |
| // enabled_features (see |Set| test for the reason). |
| std::vector<std::string> enabled_features = { |
| "caretHighlight", |
| "cursorColor", |
| "focusHighlight", |
| "stickyKeys", |
| }; |
| |
| std::vector<std::string> disabled_features = { |
| "autoclick", |
| "cursorHighlight", |
| "dockedMagnifier", |
| "highContrast", |
| "largeCursor", |
| "screenMagnifier", |
| "selectToSpeak", |
| "spokenFeedback", |
| "switchAccess", |
| "virtualKeyboard", |
| }; |
| |
| ASSERT_TRUE( |
| InitPrefServiceForTest(GetPrefs(), enabled_features, disabled_features)); |
| |
| std::string test_arg; |
| ASSERT_TRUE(GenerateTestArg("observerTest", enabled_features, |
| disabled_features, &test_arg)); |
| |
| // The test extension is supposed to report result twice when running this |
| // test. First time when in initializes it's feature listeners, and second |
| // time, when gets all expected events. This is done so the extension is |
| // running when the accessibility features are flipped; otherwise, the |
| // extension may not see events. |
| |
| bool is_mv2 = GetParam().version == ManifestVersion::kTwo; |
| const char* extension_path = is_mv2 ? kTestExtensionPathReadPermission |
| : kTestExtensionPathReadPermissionV3; |
| ASSERT_TRUE(RunExtensionTest( |
| extension_path, |
| {.custom_arg = test_arg.c_str(), .launch_as_platform_app = is_mv2})) |
| << message_; |
| |
| // This should flip all features. |
| ASSERT_TRUE( |
| InitPrefServiceForTest(GetPrefs(), disabled_features, enabled_features)); |
| |
| // Catch the second result notification sent by the test extension. |
| ResultCatcher result_catcher; |
| ASSERT_TRUE(result_catcher.GetNextResult()) << result_catcher.message(); |
| } |
| |
| } // namespace |
| |
| } // namespace extensions |